How do I operate on IP addresses and netmasks?
IPv4 addresses are 32-bit unsigned integers. The "dotted quad" notation (192.168.1.2) is only one means of representing such an address. When applying netmasks, it's easier if we first convert the dotted quad format into a plain integer.
There are several ways we can do this, but here's one of the simplest ways:
ip2int() { local o # array of octets (bytes) IFS=. read -ra o <<<"$1" echo "$(((o[0] << 24) + (o[1] << 16) + (o[2] << 8) + o[3]))" } int2ip() { echo "\ $((($1 >> 24) & 0xFF)).\ $((($1 >> 16) & 0xFF)).\ $((($1 >> 8) & 0xFF)).\ $(($1 & 0xFF))" }
Using these functions, we can convert back and forth:
$ ip2int 192.168.10.1 3232238081 $ int2ip 3232238081 192.168.10.1
To apply a netmask, we convert the netmask to an integer, and then perform a bitwise AND operation to get the network portion:
$ ip2int 255.255.255.0 4294967040 $ echo $((3232238081 & 4294967040)) 3232238080 $ int2ip 3232238080 192.168.10.0
The host portion is similar: first we negate the netmask, and then we do the same bitwise AND. After negating, we have to grab only the lowest 32 bits; otherwise, bash's 64-bit integers might give us some odd-looking results.
$ echo $((~4294967040 & 0xffffffff)) 255 $ echo $((3232238081 & 255)) 1 $ int2ip 1 0.0.0.1
The same principle applies to a netmask of any size. For example, a /23 netmask looks like:
$ int2ip $((0xfffffe00)) 255.255.254.0 $ int2ip $((2#11111111111111111111111000000000)) 255.255.254.0
The hexadecimal constant 0xfffffe00 is the same as the binary constant 2#11111111111111111111111000000000 but is more convenient to write, and easier for a human to verify.