Need help matching IP subnet with ruby regex - ruby

I am trying to match first two octets of an IP to determine network subnet.
IP start with 10.43 or 10.44 or 10.46 but not 10.45, tried to match with this expression 10.4{3|4|6} but it matches only 10.44 and 10.46
Any guess why not matching 10.43

While a regex will work (#Stefan has already provided one) and I have no idea about your implementation the IPAddr standard library may interest you e.g.
acceptable_sub_nets = ["10.43.0.0","10.44.0.0","10.46.0.0"]
my_list_of_ips.select do |ip|
acceptable_sub_nets.include?(IPAddr.new(ip).mask(16).to_s)
end
For example
IPAddr.new("10.43.22.19").mask(16).to_s
#=> "10.43.0.0"
IPAddr.new("192.168.0.1").mask(16).to_s
#=> "192.168.0.0"
Additionally you could do something like
acceptable_sub_nets = ["10.43.0.0","10.44.0.0","10.46.0.0"].map do |subnet|
IPAddr.new(subnet).mask(16).to_range
end
my_list_of_ips.select do |ip|
acceptable_sub_nets.any? {|range| range.cover?(ip) }
end
Example
subnet_range = IPAddr.new("10.43.0.0").mask(16).to_range
subnet_range.cover?("10.43.22.19")
#=> true
subnet_range.cover?("192.168.0.1")
#=> false
Update (Thank you #JordanRunning)
The second option can be simplified to
acceptable_sub_nets = [
#including the mask range
IPAddr.new("10.43.0.0/16"),
IPAddr.new("10.44.0.0/16"),
IPAddr.new("10.46.0.0/16")]
my_list_of_ips.select do |ip|
acceptable_sub_nets.any? {|range| range.include?(ip) }
end
This does not require conversion to a Range but rather leverages IPAddr#include? directly.

Related

Check whether string match or not

I have a string and an array with some strings.
as below
hostname = TETDC3DBE01
Array = ['WEB','APP','STR','DBE']
I want to find whether that hostname match with any of the array element or not?
When I'm trying with below code getting output
no
no
no
no
Here is loop repeating each and every element on array. I want check that hostname with single check on array the produce the output either yes or no only.
Array.each do |x|
if hostname.match(x)
puts "yes"
else
puts "no"
end
end
Given this fixed Ruby code:
hostname = 'TETDC3DBE01'
array = ['WEB','APP','STR','DBE']
Where if you want to find all elements in array that match as a substring of hostname your code should work. The more minimal matching system is probably:
array.select { |x| hostname.match(x) }
# => ["DBE"]
Using a tool like puts to produce output isn't always very useful because that "yes" or "no" text can't be acted upon by more code. Try and think of Ruby programs as a chain of transformations, where this selects all matches, and later you can print them, like this:
puts array.select { |x| hostname.match(x) }.join(',')
# => DBE
Check out Array#any? method.
It passes each element of the collection to the given block. The method returns true if the block ever returns a value other than false or nil. If the block is not given, Ruby adds an implicit block of { |obj| obj } that will cause any? to return true if at least one of the collection members is not false or nil.
If instead a pattern is supplied, the method returns whether pattern === element for any collection member.
In your case:
hostname = 'TETDC3DBE01'
['WEB','APP','STR','DBE'].any? do |x|
hostname.match(x)
end
or even if you actually mean equal by match:
hostname = 'TETDC3DBE01'
['WEB','APP','STR','DBE'].any?(hostname)
Lets take your code to fix it.
hostname = "TETDC3DBE01"
arr = ['WEB','APP','STR','DBE']
arr.each do |x|
if hostname.match?(x)
puts "yes"
else
puts "no"
end
end
match gives array of result and
match? gives you true or false value
I wouldn't use regexp in this case. A simple String#include? is probably faster. Furthermore any? will return true if any of the elements in the array leads is matching.
hostname = 'TETDC3DBE01'
array = ['WEB','APP','STR','DBE']
array.any? { |x| hostname.include?(x) }
#=> true
Regular expression made real easy:
hostname = "TETDC3DBE01"
array = ['WEB','APP','STR','DBE']
re = Regexp.union(array)
hostname.match?(re) # => true

Count IP addresses

I stored the following IPs in an array:
10.2.3.1
10.2.3.5
10.2.3.10 - 10.2.3.15
I'm trying to count the total number of IP addresses. The total number of IP addresses should be 8. When I iterate through the array, I get a total of three counts. I need a way to count the third item:
10.2.3.10 - 10.2.3.15
Are there any IP address counters?
If you need to convert an IP to a range, you'll need a function that converts an IPv4 value to an integer, then do math on those:
require 'ipaddr'
def ip(string)
IPAddr.new(string).to_i
end
def parse_ip(string)
string.split(/\s+\-\s+/).collect { |v| ip(v) }
end
def ip_range(string)
ips = parse_ip(string)
ips.last - ips.first + 1
end
ip_range("10.2.3.10 - 10.2.3.15")
# => 6
That should do it.
It makes perfect sense to use the IPAddr class, as #tadman did, but it should be noted that the methods in that class are not doing anything very special. #tadman's answer works just fine if his method ip (the only one to use an IPAddr method) is replaced with:
def ip(str)
str.split('.').map { |s| s.to_i.to_s(2).rjust(8,'0') }.join.to_i(2)
end
Let's compare:
require 'ipaddr'
def tadip(string)
IPAddr.new(string).to_i
end
str = "10.2.3.10"
tadip str #=> 167904010
ip str #=> 167904010
str = "255.255.255.255"
tadip str #=> 4294967295
ip str #=> 4294967295
str = "172.0.254.1"
tadip str #=> 2885746177
ip str #=> 2885746177
In fact, ip, unlike IPAddr::new works for IPv6 (32**2 bits) as well as IPv4 (32 bits) IP's (-:
str = "172.0.254.1.22.33.44.55"
tadip str #=> IPAddr::InvalidAddressError: invalid address
ip str #=> 12394185455143300151
to count interval between two IP without ipaddr library :)
int32start = "10.0.0.0".split(".").map(&:to_i).pack('CCCC').unpack('N')[0]
int32ending = "10.0.0.50".split(".").map(&:to_i).pack('CCCC').unpack('N')[0]
puts int32ending - int32start #50

Regular expression to validate IPv4 address in ruby [duplicate]

This question already has answers here:
How do I check whether a value in a string is an IP address
(13 answers)
Closed 8 years ago.
I want to verify whether a given string is valid IPv4 address or not in ruby.
I tried as follows but what is does is matches values greater than 255 as well.
How to limit each block's range from 0-255
str="255.255.255.256"
if str=~ /\b[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\b/
puts true
else
puts false
end
You have the IPAddr class in Ruby. You do not need to validate it agains a regex, just create a new object and catch the exception when it fails:
require 'ipaddr'
ip = IPAddr.new "127.0.0.1"
# => #<IPAddr: IPv4:127.0.0.1/255.255.255.255>
ip = IPAddr.new "127.0.0.a"
# => IPAddr::InvalidAddressError: invalid address
block = /\d{,2}|1\d{2}|2[0-4]\d|25[0-5]/
re = /\A#{block}\.#{block}\.#{block}\.#{block}\z/
re =~ "255.255.255.255" # => 0
re =~ "255.255.255.256" # => nil
You'd better to use ruby core class IPAddress
you could do it this way:
require "ipaddress"
IPAddress.valid? "192.128.0.12"
#=> true
IPAddress.valid? "192.128.0.260"
#=> false
you could find more here How do I check whether a value in a string is an IP address
This is a way that reads pretty easily:
str.count('.')==3 && str.split('.').all? {|s| s[/^\d{3}$/] && s.to_i < 256}

Unexpected hash's behavior

I'm newbie.
Help me, please.
I write Puppet function
Piece of code :
n_if={}
over_if = arguments[1]
over_if.each do |kk,vv|
weth={}
puts kk,vv,weth
weth = arguments[0]
weth['in_vlan'] = vv['in_vlan']
weth['options']['MTU'] = vv['mtu']
n_if['eth'+ kk.to_s]=weth
end
Data readed from 2 files, and passed into arguments[0] and arguments[1] respectively:
# template of ethernet interfaces
eth_:
method: "static"
family: "inet"
ip: ""
netmask: "255.255.0.0"
onboot: true
options:
MTU: ""
in_vlan: ""
# values for include into ethernet interfaces
eth_values:
0:
mtu: 1500
in_vlan: 15
1:
mtu: 9000
in_vlan: 125
I expect get hash with keys 'eth0' and 'eth1' as follow:
eth1methodstaticfamilyinetin_vlan125ipnetmask255.255.0.0onboottrueoptionsMTU9000eth0methodstaticfamilyinetin_vlan15ipnetmask255.255.0.0onboottrueoptionsMTU1500
But I get :
eth1methodstaticfamilyinetin_vlan125ipnetmask255.255.0.0onboottrueoptionsMTU9000eth0methodstaticfamilyinetin_vlan125ipnetmask255.255.0.0onboottrueoptionsMTU9000
What is my mistake?
First, some comments:
Your code is not indented in a way that most others do it, which makes it hard for others to help you. It should look something like this:
n_if={}
over_if = arguments[1]
over_if.each do |kk,vv|
weth={}
puts kk,vv,weth
weth = arguments[0]
weth['in_vlan'] = vv['in_vlan']
weth['options']['MTU'] = vv['mtu']
n_if['eth'+ kk.to_s]=weth
end
Perhaps your variable names make sense to you, but they don't make sense to me. What is n_if, weth, over_if, kk and vv?
You assign weth to be a hash inside your each, and then you assign it to be something else. What are you really trying to do?
You say that arguments[0] and arguments[1] are data read in from files. How are these read in? Are these YAML files? It would be helpful if you would include code to actually reproduce your problem. Pare it down to the essentials.
In Ruby it is generally more idiomatic and performant not to concatenate strings, but to use string interpolation:
n_if["eth#{kk}"] = weth
Now, some answers:
My guess is that your setup holds data like this:
arguments = {
"eth_"=>{
"method"=>"static",
"family"=>"inet",
"ip"=>"",
"netmask"=>"255.255.0.0",
"onboot"=>true,
"options"=>{"MTU"=>""},
"in_vlan"=>""
},
"eth_values"=>{
0=>{"mtu"=>1500, "in_vlan"=>15},
1=>{"mtu"=>9000, "in_vlan"=>125}
}
}
arguments[0] = arguments['eth_']
arguments[1] = arguments['eth_values']
I believe (based on many guesses as to what you have and what you may want) that your problem is this combination:
weth={}
weth=arguments[0]
I think your intent here is to say "weth is a hash type of object; now fill it with values from arguments[0]". What those lines actually say is:
Set weth to an empty hash.
Nevermind, throw away that empty hash and set weth to the same object as arguments[0].
Consequently, each time through the loop you are modifying the same hash with weth. Instead, I think you want to duplicate the hash for weth. Does the following modified code give you what you need?
n_if={}
over_if = arguments[1]
over_if.each do |kk,vv|
weth = arguments[0].dup
weth['in_vlan'] = vv['in_vlan']
weth['options']['MTU'] = vv['mtu']
n_if["eth#{kk}"]=weth
end
require 'pp' # for nice wrapping inspection
pp n_if
#=> {"eth0"=>
#=> {"method"=>"static",
#=> "family"=>"inet",
#=> "ip"=>"",
#=> "netmask"=>"255.255.0.0",
#=> "onboot"=>true,
#=> "options"=>{"MTU"=>9000},
#=> "in_vlan"=>15},
#=> "eth1"=>
#=> {"method"=>"static",
#=> "family"=>"inet",
#=> "ip"=>"",
#=> "netmask"=>"255.255.0.0",
#=> "onboot"=>true,
#=> "options"=>{"MTU"=>9000},
#=> "in_vlan"=>125}}
If not, please edit your question with more details on what you ACTUALLY have (hint: p arguments and show us the result) and what you really want as the result.
Edit: For fun, here's a functional transformation instead. It is left as an exercise to the reader to understand how it works and level-up their functional programming skills. Note that I have modified eth_values to match the hierarchy of the template so that simple merging can be applied. I've left the "MTU"=>"" and "in_vlan"=>"" entries in, but note that they are not necessary for the code to work, you could delete both (and the resulting "options"=>{}) and achieve the same result.
args = {
"eth_"=>{
"method"=>"static",
"family"=>"inet",
"ip"=>"",
"netmask"=>"255.255.0.0",
"onboot"=>true,
"options"=>{"MTU"=>""},
"in_vlan"=>""
},
"eth_values"=>{
0=>{"options"=>{"MTU"=>1500}, "in_vlan"=>15},
1=>{"options"=>{"MTU"=>9000}, "in_vlan"=>125}
}
}
n_if = Hash[
args['eth_values'].map do |num,values|
[ "eth#{num}",
args['eth_'].merge(values) do |k,v1,v2|
if v1.is_a?(Hash) and v2.is_a?(Hash) then
v1.merge(v2)
else
v2
end
end ]
end
]
pp n_if #=> Same result as in the previous code.

Netmask to CIDR in ruby

I've been using the ip-address gem and it doesn't seem to have the ability to convert from a netmask of the form
255.255.255.0
into the CIDR form
/24
Does anyone have an ideas how to quickly convert the former to the latter ?
Here is the quick and dirty way
require 'ipaddr'
puts IPAddr.new("255.255.255.0").to_i.to_s(2).count("1")
There should be proper function for that, I couldn't find that, so I just count "1"
If you're going to be using the function in a number of places and don't mind monkeypatching, this could help:
IPAddr.class_eval
def to_cidr
"/" + self.to_i.to_s(2).count("1")
end
end
Then you get
IPAddr.new('255.255.255.0').to_cidr
# => "/24"
Just as a FYI, and to keep the info easily accessible for those who are searching...
Here's a simple way to convert from CIDR to netmask format:
def cidr_to_netmask(cidr)
IPAddr.new('255.255.255.255').mask(cidr).to_s
end
For instance:
cidr_to_netmask(24) #=> "255.255.255.0"
cidr_to_netmask(32) #=> "255.255.255.255"
cidr_to_netmask(16) #=> "255.255.0.0"
cidr_to_netmask(22) #=> "255.255.252.0"
Here's a more mathematical approach, avoiding strings at all costs:
def cidr_mask
Integer(32-Math.log2((IPAddr.new(mask,Socket::AF_INET).to_i^0xffffffff)+1))
end
with "mask" being a string like 255.255.255.0. You can modify it and change the first argument to just "mask" if "mask" is already an integer representation of an IP address.
So for example, if mask was "255.255.255.0", IPAddr.new(mask,Socket::AF_INET).to_i would become 0xffffff00, which is then xor'd with 0xffffffff, which equals 255.
We add 1 to that to make it a complete range of 256 hosts, then find the log base 2 of 256, which equals 8 (the bits used for the host address), then subtract that 8 from 32, which equals 24 (the bits used for the network address).
We then cast to integer because Math.log2 returns a float.
Quick and dirty conversion:
"255.255.255.0".split(".").map { |e| e.to_i.to_s(2).rjust(8, "0") }.join.count("1").split(".")
=> I split mask in an Array
.map { |e| e.to_i.to_s(2).rjust(8, "0") }
=> For each element in Array:
.to_i
=> Convert into integer
.to_s(2)
=> Convert integer into binary
.rjust(8, "0")
=> Add padding
=> Map return a Array with same cardinality
.join
=> Convert Array into a full string
.count("1")
=> Count "1" characters => Give CIDR mask
def mask_2_ciddr mask
"/" + mask.split(".").map { |e| e.to_i.to_s(2).rjust(8, "0") }.join.count("1").to_s
end
mask_2_ciddr "255.255.255.0"
=> "/24"
mask_2_ciddr "255.255.255.128"
=> "/25"
If you don't need to use ip-address gem, you can do this with the netaddr gem
require 'netaddr'
def to_cidr_mask(dotted_mask)
NetAddr::CIDR.create('0.0.0.0/'+dotted_mask).netmask
end
to_cidr_mask("255.224.0.0") # => "/11"
require 'ipaddr'
def serialize_ipaddr(address)
mask = address.instance_variable_get(:#mask_addr).to_s(2).count('1')
"#{address}/#{mask}"
end
serialize_ipaddr(IPAddr.new('192.168.0.1/24')) # => "192.168.0.0/24"
The code achieves the masking by accessing private instance variable *#mask_addr) of IPAddr instance (address, passed into serialize_ipaddr). This is not recommended way (as the instance variables are not part of the classes public API but here it's better than parsing the string from #inspect in my opinion.
So the process is as follows:
Get the instance variable #mask_addr that represents netmask
Get its binary representation e.g. 255.255.255.0 -> 4294967040 -> 11111111111111111111111100000000
Count the 1-s in the base-2 number to get CIDR mask (24)
Make up a string consisting the address & mask
EDIT: Added explanation to the implementation as requested by NathanOliver
Here is a way to do it without the IPAddr gem
(('1'*cidr)+('0'*(32-cidr))).scan(/.{8}/m).map{|e|e.to_i(2)}.join('.')

Resources