Netmask expansion in Perl/Shell

TL;DR

Need to transform a netmask from the “number of bits” to the “full quad-dotted form”? Here’s a trick.

Sometimes you get an IPv4 Address in the form A.B.C.D/X, where X represents the number of leading bits in the netmask that are turned on in the mask.

A netmask in IPv4 can have between 0 and 32 (both included) leading bits up, i.e.:

 0 -> 00000000000000000000000000000000
 1 -> 10000000000000000000000000000000
 2 -> 11000000000000000000000000000000
 3 -> 11100000000000000000000000000000
 4 -> 11110000000000000000000000000000
 5 -> 11111000000000000000000000000000
 6 -> 11111100000000000000000000000000
 7 -> 11111110000000000000000000000000
 8 -> 11111111000000000000000000000000
 9 -> 11111111100000000000000000000000
10 -> 11111111110000000000000000000000
11 -> 11111111111000000000000000000000
12 -> 11111111111100000000000000000000
13 -> 11111111111110000000000000000000
14 -> 11111111111111000000000000000000
15 -> 11111111111111100000000000000000
16 -> 11111111111111110000000000000000
17 -> 11111111111111111000000000000000
18 -> 11111111111111111100000000000000
19 -> 11111111111111111110000000000000
20 -> 11111111111111111111000000000000
21 -> 11111111111111111111100000000000
22 -> 11111111111111111111110000000000
23 -> 11111111111111111111111000000000
24 -> 11111111111111111111111100000000
25 -> 11111111111111111111111110000000
26 -> 11111111111111111111111111000000
27 -> 11111111111111111111111111100000
28 -> 11111111111111111111111111110000
29 -> 11111111111111111111111111111000
30 -> 11111111111111111111111111111100
31 -> 11111111111111111111111111111110
32 -> 11111111111111111111111111111111

Now that we’re at it, the above expansion was generated by this command line trick:

$ perl -e 'for (0..32) {printf "%2d -> %s\n", $_, substr "1" x $_ . "0" x 32, 0, 32}'

We iterate from 0 to 32 and generate a string with the given number of leading 1s, followed by a number of 0s that make it possible to reach 32 bits overall. This is obtained by always appending 32 0s (so that we are sure to cover all cases) and then cutting the string to only keep the first 32 digits.

To generate our netmask we can use the code below:

$ NBITS=29
$ perl -le 'print join ".", unpack "C4", pack "N", 0xFFFFFFFF<<(32-shift)' $NBITS
255.255.255.248

The trick is to read it from right to left:

  • first, we build an integer corresponding to the netmask. We start from 0xFFFFFFFF (i.e. all bits are set to 1) and then shift it to the left by 32 minus the number of 1s that we have to keep (32-shift)

  • then, we turn this integer into a packed representation of a 32-bits integer (pack "N", ...)

  • the, we turn this packed representation back to four unsigned chars (unpack "C4", ...)

  • last, we merge these unsigned integers with dots (join ".", ...).

This can be turned into a shell function and a shell script, of course:

#/bin/sh

# usage: netmask <n-bits>
netmask() {
  perl -le 'print join ".", unpack "C4", pack "N", 0xFFFFFFFF<<(32-shift)' "$1"
}

! grep -- '4f77114a2f49ae89c876b4a6c229d2ca' "$0" >/dev/null 2>&1 \
  || netmask "$@"

Local version - also as snippet in GitLab.

And this is all for today!


Comments? Octodon, , GitHub, Reddit, or drop me a line!