I found the following shell functions from this topic
mask2cdr ()
{
# Assumes there's no "255." after a non-255 byte in the mask
local x=${1##*255.}
set -- 0^^^128^192^224^240^248^252^254^ $(( (${#1} - ${#x})*2 )) ${x%%.*}
x=${1%%$3*}
echo $(( $2 + (${#x}/4) ))
}
cdr2mask ()
{
# Number of args to shift, 255..255, first non-255 byte, zeroes
set -- $(( 5 - ($1 / 8) )) 255 255 255 255 $(( (255 << (8 - ($1 % 8))) & 255 )) 0 0 0
[ $1 -gt 1 ] && shift $1 || shift
echo ${1-0}.${2-0}.${3-0}.${4-0}
}
Could you explain in details how these functions convert cidr to netmask and the netmask to cidr? Specifically, the calls to set
, the parameter expansion ${#…}
, and the arithmetic expansion $((…))
are quite overwhelming.
mask2cdr()
To get the CIDR prefix from a dot-decimal netmask like this one:
you first have to convert the four octets to binary and then count the most significant bits (i.e. the number of leading ones):
This function does that rather creatively. First, we strip off all of the leading
255
octets (i.e. the octets that are all ones in binary) and store the results in variablex
:This step uses parameter expansion, which the entire script relies on pretty heavily. If we continue with our example netmask of
255.255.192.0
, we now have the following values:Next we set three variables:
$1
,$2
, and$3
. These are called positional parameters; they are much like ordinary named variables but are typically set when you pass arguments to a script or function. We can set the values directly usingset --
, for example:I prefer using named variables over positional parameters since it makes scripts easier to read and debug, but the end result is the same. We set
$1
to:This is really just a table to convert certain decimal values to binary and count the number of
1
bits. We'll come back to this later.We set
$2
toThis is called Arithmetic Expansion. It looks complex, but it is really just counting the number of
1
bits we stripped off in the first command. It breaks down to this:which in our case works out to
We stripped off two octets so we get 16. Makes sense.
We set
$3
to:which is the value of
$x
with everything after the first.
stripped off. In our case, this is192
.We need to convert this number to binary and count the number of
1
bits in it, so let's go back to our "conversion table." We can divide the table into equal chunks of four characters each:In binary, the above numbers are:
If we count from the left, each four-character block in the table corresponds to an additional
1
bit in binary. We're trying to convert192
, so let's first lop off the rightmost part of the table, from192
on, and store it inx
:The value of
$x
is nowwhich contains two four-character blocks, or two
1
bits in binary.Now we just need to add up the
1
bits from our leading255
octets (16 total, stored in variable$2
) and the1
bits from the previous step (2 total):where
is the number of characters in
$x
divided by four, i.e. the number of four-character blocks in$x
.Output:
cdr2mask()
Let's keep running with our previous example, which had a CIDR prefix of
18
.We use
set --
to set positional parameters $1 through $9:Let's examine the formulas used to set
$1
and$6
a little closer.$1
is set to:The maximum and minimum possible values for a CIDR prefix are 32 for netmask
and 0 for netmask
The above formula uses integer division, so the possible results range from 1 to 5:
$6
is set to:Let's break this down for our example CIDR prefix of
18
. First we take the modulus and do some subtraction:Next we bitwise shift 255 by this value:
This is the same as pushing six
0
bits onto the end of 255 in binary:Finally, we bitwise AND this value with 255:
which gives
or simply
Look familiar? This is the third octet in our netmask in binary:
In decimal, the value is 192.
Next we shift the positional parameters based on the value of
$1
:In our case, the value of
$1
is 3, so we shift the positional parameters 3 to the left. The previous value of$4
becomes the new value of$1
, the previous value of$5
becomes the value of$2
, and so on:These values should look familiar: they are the decimal octets from our netmask (with a couple of extra zeros tacked on at the end). To get the netmask, we simply print out the first four with dots in between them:
The
-0
after each parameter says to use0
as the default value if the parameter is not set.Output: