I would like to find out all the IPv4 networks in CIDR notation between those two networks:
10.11.3.64-10.11.3.127
10.11.52.0-10.11.52.255
IPv4 networks should have as short subnet-mask as possible.
It's fairly easy to convert 10.11.3.127
into binary, add 1
and convert back to decimal in order to get the first address of the network. Then convert 10.11.52.0
into binary, subtract 1
and convert back to decimal in order to get the last address of the network. However, any suggestions which algorithm is clever to use in order to find out the CIDR blocks inside the 10.11.3.128-10.11.51.255
range? Just a suggestion in which direction should I think would hopefully be enough :)
The CIDR number is typically preceded by a slash “/” and follows the IP address. For example, an IP address of 131.10. 55.70 with a subnet mask of 255.0. 0.0 (which has 8 network bits) would be represented as 131.10.
The formula to calculate the number of assignable IP address to CIDR networks is similar to classful networking. Subtract the number of network bits from 32. Raise 2 to that power and subtract 2 for the network and broadcast addresses. For example, a /24 network has 232-24 - 2 addresses available for host assignment.
IPv4 addresses allow a maximum of 32 bits. The same CIDR notation can be applied to IPv6 addresses. The only difference would be that IPv6 addresses can contain up to 128 bits.
The CIDR number comes from the number of ones in the subnet mask when converted to binary. The subnet mask 255.255. 255.0 is 11111111.11111111.
I really like this problem, I took a look last night and decided to give it a shot. At this point I have a proof of concept shell script working.
Disclaimer:
Another thing worth mentioning is that my understanding of:
IPv4 networks should have as short subnet-mask as possible.
is that we should try from 8 bits reserved to network up to the greatest cidr provided, in this case 25.
OK, let us see the script in action:
[root@TIAGO-TEST2 tmp]# time bash ip.sh 10.11.3.64/25 10.11.52.0/24
10.11.3.128/25
10.11.4.0/22
10.11.8.0/21
10.11.16.0/20
10.11.32.0/20
10.11.48.0/22
real 0m48.376s
user 0m6.174s
sys 0m34.644s
Below the code:
#! /bin/bash
function split_octet {
sed -re "s/\./ /g" <<< "$1"
}
function dec2bin {
perl -e 'printf "%0'"$1"'b\n",'"$2"';'
}
function bin2dec {
perl -le 'print 0b'"$1"';'
}
function ip2bin {
str=""
for octet in $(split_octet $1); do
str="${str}$(dec2bin 8 $octet)"
done
echo "$str"
}
function bin2ip {
str=""
for octet in $(grep -Eo '.{8}' <<< $1); do
dec=$(bin2dec $octet)
str="${str}.${dec}"
done
echo "$str" | sed -re 's/^\.|\.$//g'
}
function ip2dec {
ip=$1
bin2dec $(ip2bin $ip )
}
function dec2ip {
dec=$1
bin2ip $(dec2bin 32 $dec )
}
function AND {
perl -e ' $a=0b'"$1"' & 0b'"$2"';
printf "%032b\n",$a
'
}
function OR {
perl -e ' $a=0b'"$1"' | 0b'"$2"';
printf "%032b\n",$a
'
}
function NOT {
perl -le ' $a= (~ 0b'"$1"') & 0xFFFFFFFF;
printf "%032b\n",$a
'
}
function get_network {
ip=$1; mask=$2;
if [ -n "$ip" -a -n "$mask" ];then
echo $(bin2ip $(AND $(ip2bin $ip) $(ip2bin $mask)))
return
fi
grep -qP "\d+\.\d+\.\d+.\d+/\d+" <<< "$ip"
if [ "$?" == 0 ];then
ip=$(get_ip_from_cidr $1 )
mask=$(get_mask_from_cidr $1)
echo $( bin2ip $(AND $(ip2bin $ip) $(ip2bin $mask)))
fi
}
function get_broadcast {
ip=$1; mask=$2;
if [ -n "$ip" -a -n "$mask" ];then
echo $( bin2ip $(OR $(ip2bin $ip) $(NOT $(ip2bin $mask) ) ))
return
fi
grep -qP "\d+\.\d+\.\d+.\d+/\d+" <<< "$ip"
if [ "$?" == 0 ];then
ip=$(get_ip_from_cidr $1 )
mask=$(get_mask_from_cidr $1)
echo $( bin2ip $(OR $(ip2bin $ip) $(NOT $(ip2bin $mask) ) ))
fi
}
function get_ip_from_cidr {
awk -F/ '{print $1}' <<< "$1"
}
function get_mask_from_cidr {
mask=$(awk -F/ '{print $2}' <<< "$1")
mask=$(cidr $mask)
mask=$(bin2ip $mask)
echo $mask
}
function cidr {
perl -e '
$n='"$1"';
$diff=32-$n;
print "1"x$n . "0"x$diff;
'
}
snet_cidr=$1
enet_cidr=$2
largest_cidr=$(echo -e "$snet_cidr\n$enet_cidr"| awk -F/ '{print $2}' | sort -rn | head -1 )
snet_dec=$( ip2dec $(get_ip_from_cidr $snet_cidr))
enet_dec=$( ip2dec $(get_ip_from_cidr $enet_cidr))
sbc_ip=$(get_broadcast $snet_cidr)
ebc_ip=$(get_broadcast $enet_cidr)
sbc_dec=$(ip2dec $sbc_ip)
ebc_dec=$(ip2dec $ebc_ip)
counter=$sbc_dec
while [ $counter -lt $enet_dec ];do
tip=$(dec2ip $counter)
for cidr in $(seq 8 $largest_cidr) ; do
tnet_ip=$(get_network $tip/$cidr)
tnet_cidr=$tnet_ip/$cidr
tbc_ip=$(get_broadcast $tnet_cidr)
tnet_dec=$( ip2dec $(get_ip_from_cidr $tnet_cidr))
tbc_dec=$(ip2dec $tbc_ip)
if [ $sbc_dec -lt $tnet_dec -a $enet_dec -gt $tbc_dec ];then
echo $tnet_cidr
counter=$tbc_dec
break
fi
done
let counter++
done
Edit It's probably a good idea to explain what those variables are:
And wherever you see tnet or tbc is temp net, temp broadcast, temp because it's inside the loop.
If you want the shortest masks (largest networks) available, start with the lowest address (10.11.3.128) and put on the smallest mask possible, start at the next address and put on the smallest mask possible, etc. Just don't exceed the largest address of the range:
Looking at this in binary, it becomes obvious. Masks are ANDed with the subnet (any position with a zero in either the subnet or mask becomes a zero; a position must have ones in both the subnet and mask to have a one). If you AND a subnet and a mask, and it doesn't equal the subnet, it is invalid.
All IP address calculations need to be done in binary. The dotted-decimal notation is fine for human readability, but should not be used to do try to do IP address calculations.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With