1. Overview

The subnet mask, or netmask, is used to divide an IP address into a network portion and a host portion. In binary notation, the mask generally consists of a series of ones followed by zeros, where the ones cover the network bits of an IP address. The number of bits corresponding to the network portion is known as the netmask length.

In this tutorial, we’ll explore how to extract the subnet mask in Bash on a Debian system. We’ll also see how to convert the netmask length given in Classless Inter-Domain Routing (CIDR) notation to dotted decimal format.

2. Using ifconfig

We can use the now-deprecated ifconfig command to obtain information about the IP address and netmask of a network interface:

$ sudo ifconfig enp2s0
enp2s0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.0.103  netmask 255.255.255.0  broadcast 192.168.0.255
        inet6 fe80::a0b0:72d0:65cc:f141  prefixlen 64  scopeid 0x20<link>
...

In this case, the name of the network interface is enp2s0, and its IPv4 address is 192.168.0.103. The netmask is also visible as 255.255.255.0, which means in binary that the first three octets correspond to the network portion and the last octet to the host portion. That is, the binary representation of the mask takes on the form 11111111;11111111;11111111;00000000 where octets are separated by a semicolon.

We can extract the netmask address directly by piping the output to awk:

$ sudo ifconfig enp2s0 | awk '/netmask/ {print $4}'
255.255.255.0

The awk command in this case searches for the netmask keyword and prints the fourth field of the line where the keyword is found.

3. Using ip

Alternatively, we can use the ip command to show information about a network interface:

$ ip -o -4 address show enp2s0
2: enp2s0    inet 192.168.0.103/24 brd 192.168.0.255 scope global dynamic noprefixroute enp2s0\       valid_lft 84800sec preferred_lft 84800sec

The -o flag used with ip is to print concise information for each interface on one line. The -4 flag is for showing only the IPv4 information. In this case, we see that the IP address and netmask length are provided in CIDR notation as 192.168.0.103/24. Since an IPv4 address is 32 bits long, we can deduce that the netmask in binary notation consists of 24 ones followed by 8 zeros.

We can also extract the address given in CIDR notation using awk:

$ ip -o -4 address show enp2s0 | awk '{print $4}'
192.168.0.103/24

We use awk to print the fourth field of the line of information provided for the enp2s0 interface.

4. Using nmcli

We can also use the nmcli command to extract the IP address and netmask length in CIDR notation:

$ nmcli device show enp2s0 | awk '/IP4.ADDRESS/ {print $2}'
192.168.0.103/24

We use nmcli to show information about the enp2s0 network device. Then, we pipe the result to awk to print the second field of the line where the IP4.ADDRESS pattern is matched.

5. Extract Netmask Address From CIDR Notation

We can convert the netmask in CIDR notation to dotted decimal form using Bash scripting:

$ cat extract_netmask.sh
valid_ipv4() {
    local ip="${1}"
    [[ "${ip}" =~ ^(([1-9]?[0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))\.){3}([1-9]?[0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))$ ]]
    return "$?"
}

valid_cidr() {
    local ip_cidr="${1}"
    local status=1
    if [[ "${ip_cidr}" =~ ^[^/]*/([0-9]|[1-2][0-9]|3[0-2])$ ]]; then
        ip=$(echo "${ip_cidr}" | cut -d '/' -f 1)
        valid_ipv4 "${ip}" && status=0
    fi 
    return "${status}"
}

bin2ip() {
    local binary_sequence="${1}"
    echo "obase=10; ibase=2; ${binary_sequence}" | bc | xargs | tr ' ' '.'
}

cidr2netmask() {
    local ip_cidr="${1}"
    valid_cidr "${ip_cidr}" || return 1
    netmask_length="$(echo "${ip_cidr}" | cut -d '/' -f 2)"
    host_length="$((32-netmask_length))"
    ones="$(head -c "${netmask_length}" /dev/zero | tr '\0' '1')"
    zeros="$(head -c "${host_length}" /dev/zero | tr '\0' '0')"
    sequence="$(sed -E 's/(.{8})(.{8})(.{8})(.{8})/\1;\2;\3;\4/' <<< "${ones}${zeros}")"
    bin2ip "${sequence}"
}

The script consists of four functions. Let’s delve into what each function does.

5.1. valid_ipv4() and valid_cidr()

The first function, named valid_ipv4(), validates whether an address is an IPv4 address using a single regular expression.

The second function, named valid_cidr(), builds on the first function to validate whether an IPv4 address is in CIDR notation. In particular, it performs several steps:

  1. we save the function’s first argument in a variable named ip_cidr and initialize the status variable to 1, signifying an invalid CIDR address
  2. a regular expression validates that ip_cidr consists of characters followed by a forward slash (/) and a number in [0-32] corresponding to the netmask length; if the test fails, we return the status variable, otherwise, we proceed to step 3
  3. the cut command extracts characters preceding the forward slash, and we save the result in a variable named ip
  4. valid_ipv4() checks that ip is a valid IPv4 address, and if so, we change the status variable to 0
  5. the function returns the value of the status variable

This way, the function returns with an exit value of 0, indicating success, only if the IP address is a valid IPv4 address and the netmask length is a valid integer in the range 0 to 32.

5.2. bin2ip()

The third function, bin2ip(),* converts sequences of binary bits separated by a semicolon, such as 10000000;00101010;00000101;00000100 into dotted decimal format such as *128.42.5.4.

In particular, we first use the bc command to perform the conversion from base 2 to base 10. Then, we pipe the output to xargs so that it prints on a single line. Finally, we use the tr command to convert spaces into dots.

5.3. cidr2netmask()

Finally, the fourth function, cidr2netmask(), uses the previous three to validate an IPv4 address in CIDR notation and compute the netmask address. It implements a number of steps, combining our previous code:

  1. we save the function’s first argument in a variable named ip_cidr
  2. valid_cidr() checks that ip_cidr is a valid CIDR IPv4 address and returns with an exit status of 1 if the validation fails
  3. the cut command extracts the netmask length from ip_cidr and saves it in a variable named netmask_length
  4. we use arithmetic expansion to compute the number of bits corresponding to the host portion as 32 – netmask_length, and we save the result in a variable named host_length
  5. we generate a series of ones, named ones, as long in length as the value of the netmask_length variable
  6. we generate a series of zeros, named zeros, as long in length as the value of the host_length variable
  7. sed executes over the ${ones}${zeros} input to capture every octet and separate it from the following one by a semicolon
  8. bin2ip() executes over the sequence created using sed from the previous step

In the generation steps, we use head -c over the /dev/zero device to generate a stream of null characters, and then we pipe these to tr to convert them into ones or zeros as required.

5.4. Testing the Functions

Let’s source the extract_netmask.sh script:

$ . ./extract_netmask.sh

Now, we can test the valid_ipv4() function:

$ valid_ipv4 128.42.5.4 && echo 'valid' || echo 'invalid'
valid

Likewise, we can test the valid_cidr() function:

$ valid_cidr 128.42.5.4/21 && echo 'valid' || echo 'invalid'
valid

Next, we test the bin2ip() function:

$ bin2ip '10000000;00101010;00000101;00000100'
128.42.5.4

Finally, let’s test the main cir2netmask() function:

$ cidr2netmask 128.42.5.4/21
255.255.248.0

Subsequently, we see that the netmask length of 21 is converted to 255.255.248.0.

6. Conclusion

In this article, we explored different methods for extracting the netmask using network commands such as ifconfigip, and nmcli. We also learned how to convert the netmask of an IPv4 address given in CIDR notation to dotted decimal format.