Class: IPAdmin::Tree

Inherits:
Object
  • Object
show all
Defined in:
lib/ip_admin.rb

Overview

A class & series of methods for creating and manipulating IP-based

heirarchical trees. Both IPv4 and IPv6 are supported.

Tree's are useful for creating mini 'routing tables' of CIDR address space.
Using a tree, you can quickly determine the 'route' of another CIDR via
the standard longest-match algorithm.

Tree's are not dynamic, in that if you modify any of the CIDR objects
contained within, the Tree will not automatically adjust itself accordingly.
For example if you have a tree like the following:

   192.168.0.0/24
       192.168.0.0/26
   192.168.1.0/24

You decide to resize 192.168.0.0/24 to 192.168.0.0/23. The tree will now
be incorrect:

   192.168.0.0/23
       192.168.0.0/26
   192.168.1.0/24

You would need to remove and re-add the CIDR 192.168.0.0/23 in order for the
tree to rebuild itself properly.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options) ⇒ Tree

  • Arguments:

    • Hash with the following fields:

      - :Version -- IP version for this tree (4 or 6)
      

Example:

table = IPAdmin::CIDRTable.new(:Version => 4)


2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
# File 'lib/ip_admin.rb', line 2953

def initialize(options)
    unless (options.kind_of? Hash)
        raise ArgumentError, "Expected Hash, but #{options.class} provided."
    end

    if ( options.has_key?(:Version) )
        @version =  options[:Version]

        unless ( @version == 4 || @version == 6 )
            raise "IP version should be either 4 or 6."
        end
    else
        raise ArgumentError, "Missing argument: Version."
    end

    if (@version == 4)
        @max_bits = 32
        @all_f = 2**@max_bits - 1
    else
        @max_bits = 128
        @all_f = 2**@max_bits - 1
    end

    # root of our ordered IP tree
    @root = []

end

Instance Attribute Details

#versionObject (readonly)

IP version for this tree (4 or 6)



2935
2936
2937
# File 'lib/ip_admin.rb', line 2935

def version
  @version
end

Instance Method Details

#add(object) ⇒ Object

Add an IPAdmin::CIDR object to the tree.

  • Arguments:

    • IPAdmin::CIDR object

  • Returns:

    • nothing

Example:

tree.add(cidr)


3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
# File 'lib/ip_admin.rb', line 3001

def add(object)

    # validate object
    unless ( object.kind_of?(IPAdmin::CIDR) )
        raise ArgumentError, "IPAdmin::CIDR object " +
                             "required but #{object.class} provided."
    end

    unless (object.version == @version )
        raise "IP version #{object.version} is incompatible with " +
              "Tree IP version #{@version}."
    end

    net_struct = IPAdmin.create_net_struct(object)

    # search tree for it's home branch
    search_results = find_home(net_struct, @root)
    home_struct = search_results[0] if (search_results)
    home_branch = home_struct.subnets if (search_results)
    home_branch = @root if (!home_branch)

    # make sure we dont have a duplicate
    if (home_struct)
        if ( (net_struct.network == home_struct.network) && 
             (net_struct.netmask == home_struct.netmask) )
            raise "Attempted to add #{object.desc()} multiple times."
        end
    end

    # now that we have our branch location, check that other entries
    # of this branch are not subnets of our new object. if they are
    # then make them a branch of our new object
    home_branch.each do |entry|
        is_contained = contains(net_struct, entry)

        if (is_contained)
            net_struct.subnets.push(entry)
        end
    end

    net_struct.subnets.each do |entry|
        home_branch.delete(entry)
    end

    # add new object as an ordered entry to home_branch
    add_to_branch(net_struct,home_branch)

    return(nil)
end

#collapseObject

Collapse (supernet) all subnets of the tree, and return a new tree. The supernetting of subnets will only occur in situations where the newly created supernet will not result in the ‘creation’ of additional space. For example the following blocks (192.168.0.0/24, 192.168.1.0/24, and 192.168.2.0/24) would merge into 192.168.0.0/23 and 192.168.2.0/24 rather than into 192.168.0.0/22.

  • Arguments:

    • none

  • Returns:

    • IPAdmin::Tree object

Example:

new_tree = tree.collapse()


3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
# File 'lib/ip_admin.rb', line 3117

def collapse()
        tree = IPAdmin::Tree.new(:Version => @version)
        branch = IPAdmin.merge(:List => @root, :NetStruct => 1)
        dumped = dump_branch(branch)
        dumped.each do |entry|
            net_struct = entry[:NetStruct]

            if (@version == 4)
                network = IPAdmin.unpack_ipv4_addr(net_struct.network)
                netmask = IPAdmin.unpack_ipv4_netmask(net_struct.netmask)
            else
                network = IPAdmin.unpack_ipv6_addr(net_struct.network)
                netmask = IPAdmin.unpack_ipv6_netmask(net_struct.netmask)
            end
            cidr = IPAdmin::CIDR.new(:CIDR => "#{network}/#{netmask}")
            tree.add(cidr)
        end
    return(tree)
end

#dumpObject

Dump the contents of this tree.

  • Arguments:

    • none

  • Returns:

    • ordered array of hashes with the following fields:

      - :Object => CIDR object
      - :Depth => (depth level in tree)
      

Example:

dumped = tree.dump()


3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
# File 'lib/ip_admin.rb', line 3210

def dump()
    list = []
    dumped = dump_branch(@root)

    dumped.each do |entry|
        depth = entry[:Depth]
        net_struct = entry[:NetStruct]
        object = net_struct.object
        list.push({:Depth => depth, :Object => object})
    end

    return(list)
end

#exists?(object) ⇒ Boolean

Has an IPAdmin::CIDR object already been added to the tree?

  • Arguments:

    • IPAdmin::CIDR object

  • Returns:

    • true or false

Example:

added = tree.exists?(cidr)

Returns:

  • (Boolean)


3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
# File 'lib/ip_admin.rb', line 3281

def exists?(object)

    # validate object
    unless ( object.kind_of?(IPAdmin::CIDR) )
        raise ArgumentError, "IPAdmin::CIDR object " +
                             "required but #{object.class} provided."
    end

    unless (object.version == @version )
        raise "IP version #{object.version} is incompatible with " +
              "Tree IP version #{@version}."
    end

    net_struct = IPAdmin.create_net_struct(object)
    home_struct,home_branch = find_home(net_struct, @root)

    # check for match
    found = false
    if (home_struct)  
        if ( (net_struct.network == home_struct.network) && 
             (net_struct.netmask == home_struct.netmask) )
            found = true
        end
    end


    return(found)
end

#find(object) ⇒ Object

Find the longest matching branch of our tree to which an IPAdmin::CIDR object belongs. Useful for performing ‘routing table’ lookups.

  • Arguments:

    • IPAdmin::CIDR object

  • Returns:

    • IPAdmin::CIDR object, or nil if not found

Example:

longest_match = tree.find(ip)


3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
# File 'lib/ip_admin.rb', line 3331

def find(object)
    unless ( object.kind_of?(IPAdmin::CIDR) )
        raise ArgumentError, "IPAdmin::CIDR object " +
                             "required but #{object.class} provided."
    end

    unless (object.version == @version )
        raise "IP version #{object.version} is incompatible with " +
              "Tree IP version #{@version}."
    end

    net_struct = IPAdmin.create_net_struct(object)
    home_struct,home_branch = find_home(net_struct, @root)
    found_obj = home_struct.object if (home_struct)

    return(found_obj)
end

#find_space(options) ⇒ Object

Find Tree entries that can hold a subnet of size X. Returns only the smallest matching subnets of each supernet. If neither IPCount or Subnet or provided, then method returns all entries that can fit a single IP.

  • Arguments:

    • Hash with the following fields:

      - :IPCount -- Minimum number of IP's that should fit within subnet (optional)
      - :Limit -- Maximum entries to return (optional)
      - :Subnet -- Netmask (in bits) of space to find(optional)
      
  • Returns:

    • ordered array of IPAdmin::CIDR objects

Note:

:Subnet always takes precedence over :IPCount.

Example:

list = tree.find_space(:Subnet => 27)
list = tree.find_space(:IPCount => 33, :Limit => 2)


3426
3427
3428
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484
3485
3486
3487
3488
3489
3490
3491
# File 'lib/ip_admin.rb', line 3426

def find_space(options)
    limit = nil
    list = []

    # validate options
    unless (options.kind_of? Hash)
        raise ArgumentError, "Expected Hash, but #{options.class} provided."
    end

    if ( options.has_key?(:Limit) )
        limit = options[:Limit]
    end
    
    if ( options.has_key?(:IPCount) )
        num_ips = options[:IPCount]
        bits_needed = 0
        until (2**bits_needed >= num_ips)
            bits_needed += 1
        end
        subnet_size = @max_bits - bits_needed
    end
    
    if ( options.has_key?(:Subnet) )
        subnet_size = options[:Subnet]
    end

    # check that subnet_size is a valid size
    if (@version == 4)
        subnet_size = 32 if (!subnet_size)
        unless ( (subnet_size > 0) && (subnet_size < 33) )
            raise "#{subnet_size} is out of range for an " +
                  "IP version #{@version} Tree."
        end

    else
        subnet_size = 128 if (!subnet_size)
        unless ( (subnet_size > 0) && (subnet_size < 129) )
            raise "#{subnet_size} is out of range for an " +
                  "IP version #{@version} Tree."
        end
    end

    search_list = dump_branch(@root)

    search_list.each do |entry|
        depth = entry[:Depth]
        net_struct = entry[:NetStruct]

        # we only want leaf nodes of type IPAdmin::CIDR
        if ( (net_struct.subnets.length == 0) &&
             (net_struct.object.kind_of?(IPAdmin::CIDR)) )

             if (subnet_size >= net_struct.object.bits)
                list.push(net_struct.object)
             end
        end

        if (limit && (list.length >= limit) )
            break
        end

    end

    return(list)

end

#prune(object) ⇒ Object

Remove an IPAdmin::CIDR object, and all child objects from the tree.

  • Arguments:

    • IPAdmin::CIDR object

  • Returns:

    • true on success, or false

Example:

did_prune = tree.prune(ip)


3514
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
3539
3540
# File 'lib/ip_admin.rb', line 3514

def prune(object)
    unless ( object.kind_of?(IPAdmin::CIDR) )
        raise ArgumentError, "IPAdmin::CIDR object " +
                             "required but #{object.class} provided."
    end

    unless (object.version == @version )
        raise "IP version #{object.version} is incompatible with " +
              "Tree IP version #{@version}."
    end

    net_struct = IPAdmin.create_net_struct(object)
    home_struct,home_branch = find_home(net_struct, @root)

    if(!home_struct)
        raise "#{object.desc} could not be found."
    end
    
    # remove if home_struct.object = object
    pruned = false
    if (home_struct.object = object)
        home_branch.delete(home_struct)
        pruned = true
    end

    return(pruned)
end

#remove(object) ⇒ Object

Remove a single IPAdmin::CIDR object from the tree.

  • Arguments:

    • IPAdmin::CIDR object

  • Returns:

    • true on success, or false

Example:

did_remove = tree.remove(ip)


3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573
3574
3575
3576
3577
3578
3579
3580
3581
3582
3583
3584
3585
3586
3587
3588
3589
3590
3591
3592
3593
3594
3595
3596
3597
3598
3599
# File 'lib/ip_admin.rb', line 3562

def remove(object)
    unless ( object.kind_of?(IPAdmin::CIDR) )
        raise ArgumentError, "IPAdmin::CIDR object " +
                             "required but #{object.class} provided."
    end

    unless (object.version == @version )
        raise "IP version #{object.version} is incompatible with " +
              "Tree IP version #{@version}."
    end

    net_struct = IPAdmin.create_net_struct(object)
    home_struct,home_branch = find_home(net_struct, @root)

    # remove if home_struct.object = object
    removed = false
    if (home_struct.object = object)

        # if we have children subnets, move them up one level
        if (home_struct.subnets.length > 0)
            home_struct.subnets.each do |entry|
                index = 0
                home_branch.each do
                    if(entry.network < (home_branch[index]).network)
                        break
                    end
                    index += 1
                end
                home_branch.insert(index, entry)
            end
        end

        home_branch.delete(home_struct)
        removed = true
    end

    return(removed)
end

#showObject

Print the tree in a nicely formatted string.

  • Arguments:

    • none

  • Returns:

    • String representing the contents of the tree

Example:

puts tree.show()


3621
3622
3623
3624
3625
3626
3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
3648
3649
# File 'lib/ip_admin.rb', line 3621

def show()
    printed = ""
    list = dump_branch(@root)

    list.each do |entry|
        net_struct = entry[:NetStruct]
        depth = entry[:Depth]

        if (@version == 4)
            network = IPAdmin.unpack_ipv4_addr(net_struct.network)
            netmask = IPAdmin.unpack_ipv4_netmask(net_struct.netmask)

        else
            network = IPAdmin.unpack_ipv6_addr(net_struct.network)
            netmask = IPAdmin.unpack_ipv6_netmask(net_struct.netmask)
            network = IPAdmin.shorten(network)
        end

        if (depth == 0)
            indent = ""
        else
            indent = " " * (depth*3)
        end

        printed << "#{indent}#{network}/#{netmask}\n"
    end

    return(printed)
end