Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cidr_to_range function #168

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
### Functions

* [`extlib::cache_data`](#extlibcache_data): Retrieves data from a cache file, or creates it with supplied data if the file doesn't exist
* [`extlib::cidr_to_range`](#extlibcidr_to_range): Converts an CIDR address of the form 192.168.0.1/24 into a range of IP address excluding the network and broadcast addresses.
* [`extlib::default_content`](#extlibdefault_content): Takes an optional content and an optional template name and returns the contents of a file.
* [`extlib::dir_split`](#extlibdir_split): Splits the given directory or directories into individual paths.
* [`extlib::dump_args`](#extlibdump_args): Prints the args to STDOUT in Pretty JSON format.
Expand Down Expand Up @@ -93,6 +94,40 @@ Data type: `Any`

The data for when there is no cache yet

### `extlib::cidr_to_range`

Type: Ruby 4.x API

Imported by Tim 'bastelfreak' Meusel into voxpupuli/extlib because Yelp/netstdlib got abandoned
igalic marked this conversation as resolved.
Show resolved Hide resolved

#### Examples

##### calling the function

```puppet
extlib::cidr_to_range('127.0.0.1/8')
```

#### `extlib::cidr_to_range(Variant[Stdlib::IP::Address::V4::CIDR,Stdlib::IP::Address::V6::CIDR] $ip)`

Imported by Tim 'bastelfreak' Meusel into voxpupuli/extlib because Yelp/netstdlib got abandoned

Returns: `Variant[Array[Stdlib::IP::Address::V4::Nosubnet],Array[Stdlib::IP::Address::V6::Nosubnet]]` IPv6 or IPv4 ip range without net/broadcast

##### Examples

###### calling the function

```puppet
extlib::cidr_to_range('127.0.0.1/8')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I sincerely hope no one calls this with a /8...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure that's much worse than the standard /64 IPv6 address range…

Copy link
Member

@ekohl ekohl Oct 7, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, IPv4 /8 is 2**24 while IPv6 /64 is 2**64. Every IPv4 address is 32 bits. 32 bits * 2**24 = 67 MB (assuming optimal storage, which it isn't). For IPv6 it's 128 bits * 2**64 = 256 EiB. If my math is correct, you really don't want to do this for IPv6.

```

##### `ip`

Data type: `Variant[Stdlib::IP::Address::V4::CIDR,Stdlib::IP::Address::V6::CIDR]`

IPv6 or IPv4 address in CIDR notation

### `extlib::default_content`

Type: Ruby 4.x API
Expand Down
21 changes: 21 additions & 0 deletions lib/puppet/functions/extlib/cidr_to_range.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Imported by Tim 'bastelfreak' Meusel into voxpupuli/extlib because Yelp/netstdlib got abandoned
#
# @summary Converts an CIDR address of the form 192.168.0.1/24 into a range of IP address excluding the network and broadcast addresses.
#
Puppet::Functions.create_function(:'extlib::cidr_to_range') do
# @param ip IPv6 or IPv4 address in CIDR notation
# @return IPv6 or IPv4 ip range without net/broadcast
# @example calling the function
# extlib::cidr_to_range('127.0.0.1/8')
dispatch :cidr_to_range do
param 'Variant[Stdlib::IP::Address::V4::CIDR,Stdlib::IP::Address::V6::CIDR]', :ip
return_type 'Variant[Array[Stdlib::IP::Address::V4::Nosubnet],Array[Stdlib::IP::Address::V6::Nosubnet]]'
end

def cidr_to_range(ip)
ips = IPAddr.new(ip).to_range.map(&:to_s)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, this can be slow and memory consuming for huge IPv6 networks like a /64 (or larger). Why would you want this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, depending on the subnet size it might take some time.


I know at least one person that uses https://github.com/Yelp/puppet-netstdlib and I require those functions as well. Yelp abandoned the module and I had the impression it's a good idea to add them to extlib. I thought for a hand full of short functions it doesnt make much sense to maintain a whole module.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this used for? eespecially in the case of IPv6?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, not sure why you would want to enumerate every address of a subnet (you're definitely not doing this for most IPv6 networks), but I think it would be more useful to have a function that gets one of the addresses in a subnet as I mentioned here: #165 (comment)

You could have a loop around such a function if you really wanted to do something with every address.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this would make sense if we returned lazy structures

ips.shift
ips.pop
ips
Comment on lines +16 to +19
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only really true for IPv4, not IPv6.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if I can follow. I tested this locally and also wrote a test that passes for IPv6. This works for IPv4 and IPv6?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Popping the last address isn't really needed AFAIK because IPv6 doesn't have a broadcast address. I think the same is true for shifting due to a network address.

Copy link
Member

@kenyon kenyon Oct 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first address in an IPv6 subnet is reserved as the subnet router anycast address: https://tools.ietf.org/html/rfc4291#section-2.6.1

But the last address in a subnet is usable.

end
end
35 changes: 35 additions & 0 deletions spec/functions/extlib/cidr_to_range.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
require 'spec_helper'

describe 'extlib::cidr_to_range' do
it 'exists' do
is_expected.not_to be_nil
end

context 'when called with no parameters' do
it { is_expected.to run.with_params.and_raise_error(ArgumentError) }
end

context 'when called with a Integer' do
it { is_expected.to run.with_params(42).and_raise_error(ArgumentError) }
end

context 'when called with a String thats not an ip address' do
it { is_expected.to run.with_params('42').and_raise_error(ArgumentError) }
end

context 'when called with an IP Address that is not in the CIDR notation' do
it { is_expected.to run.with_params('127.0.0.1').and_raise_error(ArgumentError) }
end

context 'when called with an IP Address that is not in the CIDR notation' do
it { is_expected.to run.with_params('fe80::800:27ff:fe00:0').and_raise_error(ArgumentError) }
end

context 'when called with an IPv4 CIDR' do
it { is_expected.to run.with_params('127.0.0.0/30').and_return(['127.0.0.1', '127.0.0.2']) }
end

context 'when called with an IPv6 CIDR' do
it { is_expected.to run.with_params('fe80::5054:ff:fe47:4a37/126').and_return(['fe80::5054:ff:fe47:4a35', 'fe80::5054:ff:fe47:4a36']) }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I always like to use example IP ranges. In https://community.theforeman.org/t/using-ips-and-domains-in-tests-documentation/16600 I keep a list of references.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed this is not correct for IPv6, the last address fe80::5054:ff:fe47:4a37 is usable.

end
end