- Section
- Linux
- Updated
- 19 Aug 2026
- Examples
- 17
An address written as 10.42.7.19/22 does not say on its face which other
addresses share its network. The /22 decides that, and reading it wrong puts
hosts on the far side of a router with no obvious symptom.
This covers what the prefix means, how to convert between a prefix and a
netmask, and which commands answer the question on Linux and macOS — ipcalc,
sipcalc, python3, and plain Bash arithmetic when nothing else is installed.
What the prefix means
An IPv4 address is 32 bits, usually written as four decimal octets. The prefix after the slash is the count of leading bits that identify the network. The bits left over identify hosts inside it.
For 192.168.1.100/26, 26 bits are network and 6 are host, so the block holds
26 = 64 addresses. The lowest is the network address and the highest
is the broadcast address. Neither can be assigned to a host, which leaves 62.
The same split written as a netmask is a 32-bit number with the network bits set
to 1: /26 is 255.255.255.192. The two notations carry identical information.
💡 The count of assignable addresses is
2^(32 - prefix) - 2. The two exceptions are at the bottom of the table below.
| Prefix | Netmask | Addresses | Usable hosts |
|---|---|---|---|
/8 | 255.0.0.0 | 16777216 | 16777214 |
/12 | 255.240.0.0 | 1048576 | 1048574 |
/16 | 255.255.0.0 | 65536 | 65534 |
/22 | 255.255.252.0 | 1024 | 1022 |
/23 | 255.255.254.0 | 512 | 510 |
/24 | 255.255.255.0 | 256 | 254 |
/25 | 255.255.255.128 | 128 | 126 |
/26 | 255.255.255.192 | 64 | 62 |
/27 | 255.255.255.224 | 32 | 30 |
/28 | 255.255.255.240 | 16 | 14 |
/29 | 255.255.255.248 | 8 | 6 |
/30 | 255.255.255.252 | 4 | 2 |
/31 | 255.255.255.254 | 2 | 2 |
/32 | 255.255.255.255 | 1 | 1 |
A /31 has no room for a network and broadcast pair, so
RFC 3021 drops the reservation and
both addresses are usable. It is the normal choice for a point-to-point link. A
/32 is a single host route.
Reading the address you already have
On Linux, ip addr reports the prefix directly. The -o flag keeps each
interface on one line, and scope global filters out loopback.
$ ip -o -4 addr show scope global
482: eth0 inet 172.17.0.5/16 brd 172.17.255.255 scope global eth0\ valid_lft forever preferred_lft forevermacOS has no ip command. Use ifconfig, which prints the netmask in
hexadecimal rather than dotted decimal:
$ ifconfig en0 | grep 'inet '
inet 192.168.1.42 netmask 0xffffff00 broadcast 192.168.1.2550xffffff00 is 24 one-bits followed by 8 zero-bits, so it is /24. Counting
that by hand every time gets old. This function accepts either notation:
mask2prefix() {
local m=$1 v a b c d p=0 i
if [[ $m == 0x* ]]; then
v=$(( m ))
else
local IFS=.
read -r a b c d <<< "$m"
v=$(( (a<<24)|(b<<16)|(c<<8)|d ))
fi
for ((i=31; i>=0; i--)); do
if (( (v>>i) & 1 )); then p=$((p+1)); else break; fi
done
echo "/$p"
}$ mask2prefix 0xffffff00
/24
$ mask2prefix 0xfffffc00
/22
$ mask2prefix 255.255.255.192
/26
$ mask2prefix 255.240.0.0
/12⚠️ Write that loop with an explicit
if, not with(( (v>>i) & 1 )) && ((p++)) || break. The shorter form is silently wrong:((p++))returns the value before the increment, so the first0to1step evaluates false and triggers thebreak, and every mask comes back as/1.
Calculating a subnet with ipcalc and sipcalc
ipcalc is the fastest way to get every field at once. It prints the binary
alongside the decimal, which makes the network and host split visible.
$ ipcalc 192.168.1.100/26
Address: 192.168.1.100 11000000.10101000.00000001.01 100100
Netmask: 255.255.255.192 = 26 11111111.11111111.11111111.11 000000
Wildcard: 0.0.0.63 00000000.00000000.00000000.00 111111
=>
Network: 192.168.1.64/26 11000000.10101000.00000001.01 000000
HostMin: 192.168.1.65 11000000.10101000.00000001.01 000001
HostMax: 192.168.1.126 11000000.10101000.00000001.01 111110
Broadcast: 192.168.1.127 11000000.10101000.00000001.01 111111
Hosts/Net: 62 Class C, Private InternetThe space in the binary column marks the prefix boundary. Everything left of it is fixed for the whole subnet; everything right of it counts up from network to broadcast.
Ignore Class C on the last line. Address classes fixed the network and host
split by the value of the first octet, and CIDR replaced that scheme with an
explicit prefix in 1993. Classful addressing has been formally obsolete since
RFC 4632, and the label survives in
tool output as a historical artifact. A /26 is a /26 regardless of what
class the address would once have fallen into.
sipcalc reports the same block in a flatter format, and gives the decimal and
hexadecimal forms of the address:
$ sipcalc 192.168.1.100/26
-[ipv4 : 192.168.1.100/26] - 0
[CIDR]
Host address - 192.168.1.100
Host address (decimal) - 3232235876
Host address (hex) - C0A80164
Network address - 192.168.1.64
Network mask - 255.255.255.192
Network mask (bits) - 26
Network mask (hex) - FFFFFFC0
Broadcast address - 192.168.1.127
Cisco wildcard - 0.0.0.63
Addresses in network - 64
Network range - 192.168.1.64 - 192.168.1.127
Usable range - 192.168.1.65 - 192.168.1.126Neither ships by default. Install them with apt install ipcalc sipcalc on
Debian and Ubuntu, or brew install ipcalc sipcalc on macOS.
When ipcalc is not installed
Python’s ipaddress module is in the standard library, so any box with Python 3
already has a subnet calculator:
$ python3 -c "
import ipaddress
n = ipaddress.ip_network('192.168.1.100/26', strict=False)
print('Network: ', n.network_address)
print('Netmask: ', n.netmask)
print('Broadcast:', n.broadcast_address)
print('Hosts: ', n[1], '-', n[-2], f'({n.num_addresses - 2} usable)')"
Network: 192.168.1.64
Netmask: 255.255.255.192
Broadcast: 192.168.1.127
Hosts: 192.168.1.65 - 192.168.1.126 (62 usable)strict=False is what allows a host address rather than the network address as
input. Without it, 192.168.1.100/26 raises ValueError.
On a minimal image with no Python, the arithmetic is short enough to do in the shell. Converting the address to a 32-bit integer makes the mask a plain bitwise AND:
subnetinfo() {
local IFS=. addr=${1%/*} prefix=${1#*/} a b c d
ip2int() { local IFS=. ; read -r a b c d <<< "$1"; echo $(( (a<<24)|(b<<16)|(c<<8)|d )); }
int2ip() { local n=$1; echo "$(( (n>>24)&255 )).$(( (n>>16)&255 )).$(( (n>>8)&255 )).$(( n&255 ))"; }
local ip mask network broadcast
ip=$(ip2int "$addr")
mask=$(( prefix == 0 ? 0 : (0xFFFFFFFF << (32 - prefix)) & 0xFFFFFFFF ))
network=$(( ip & mask ))
broadcast=$(( network | (~mask & 0xFFFFFFFF) ))
printf 'Address: %s/%s\n' "$addr" "$prefix"
printf 'Netmask: %s\n' "$(int2ip "$mask")"
printf 'Network: %s\n' "$(int2ip "$network")"
printf 'Broadcast: %s\n' "$(int2ip "$broadcast")"
printf 'Hosts: %s - %s (%s usable)\n' \
"$(int2ip $((network+1)))" "$(int2ip $((broadcast-1)))" "$(( broadcast - network - 1 ))"
}$ subnetinfo 192.168.1.100/26
Address: 192.168.1.100/26
Netmask: 255.255.255.192
Network: 192.168.1.64
Broadcast: 192.168.1.127
Hosts: 192.168.1.65 - 192.168.1.126 (62 usable)
$ subnetinfo 10.42.7.19/22
Address: 10.42.7.19/22
Netmask: 255.255.252.0
Network: 10.42.4.0
Broadcast: 10.42.7.255
Hosts: 10.42.4.1 - 10.42.7.254 (1022 usable)Drop it in ~/.bashrc and it is available without installing anything. Note the
& 0xFFFFFFFF on the mask and on the complement: Bash integers are 64-bit, so
without it the left shift and the bitwise NOT both leak bits above bit 31.
Splitting a network into smaller subnets
Carving a block into equal pieces is the usual reason to reach for any of this — separating a VLAN, or dividing a VPC range without overlapping. Each step up in prefix halves the block:
$ python3 -c "
import ipaddress
for s in ipaddress.ip_network('192.168.1.0/24').subnets(new_prefix=26):
print(f'{s} hosts {s.network_address+1} - {s.broadcast_address-1}')"
192.168.1.0/26 hosts 192.168.1.1 - 192.168.1.62
192.168.1.64/26 hosts 192.168.1.65 - 192.168.1.126
192.168.1.128/26 hosts 192.168.1.129 - 192.168.1.190
192.168.1.192/26 hosts 192.168.1.193 - 192.168.1.254The boundaries land on multiples of 64 because a /26 is 64 addresses wide. A
subnet always starts on a multiple of its own size, which is why 192.168.1.100
belongs to the block starting at .64 rather than at .100.
Checking whether an address falls inside a block
This is the question behind most firewall rules, allowlists and access controls, and it is a containment test rather than a calculation:
$ python3 -c "import ipaddress; print(ipaddress.ip_address('192.168.1.130') in ipaddress.ip_network('192.168.1.128/26'))"
TrueIn Bash, mask both addresses and compare — if the network bits match, they are in the same block:
in_net() {
local IFS=. ip=$1 cidr=$2 a b c d
read -r a b c d <<< "$ip"; local i=$(( (a<<24)|(b<<16)|(c<<8)|d ))
read -r a b c d <<< "${cidr%/*}"; local n=$(( (a<<24)|(b<<16)|(c<<8)|d ))
local p=${cidr#*/} m
m=$(( p == 0 ? 0 : (0xFFFFFFFF << (32 - p)) & 0xFFFFFFFF ))
(( (i & m) == (n & m) ))
}$ in_net 192.168.1.130 192.168.1.0/26 && echo inside || echo outside
outside
$ in_net 192.168.1.130 192.168.1.128/26 && echo inside || echo outside
inside
$ in_net 10.42.7.19 10.0.0.0/8 && echo inside || echo outside
insidePrivate address ranges
RFC 1918 reserves three ranges for internal networks. They are not routed on the public internet, so they are the ranges to subnet for a LAN, a lab, or a container bridge.
| Range | First address | Last address | Addresses |
|---|---|---|---|
10.0.0.0/8 | 10.0.0.0 | 10.255.255.255 | 16777216 |
172.16.0.0/12 | 172.16.0.0 | 172.31.255.255 | 1048576 |
192.168.0.0/16 | 192.168.0.0 | 192.168.255.255 | 65536 |
The 172.16.0.0/12 range is the one most often misread. It ends at
172.31.255.255, not at 172.16.255.255, so 172.20.0.0/16 is private and
172.32.0.0/16 is not.
A fourth range worth recognizing is 100.64.0.0/10, reserved by
RFC 6598 for carrier-grade NAT. It
covers 100.64.0.0 through 100.127.255.255. It is not RFC 1918 space and not
free to use internally, but it shows up on connections behind a carrier NAT and
inside some cloud and VPN products.
Troubleshooting
Two hosts answering for one address. Check the neighbor table for the MAC address currently bound to it. On Linux:
$ ip neigh show
172.17.0.1 dev eth0 lladdr 02:42:36:20:6b:9e REACHABLEarping -D -I eth0 192.168.1.100 probes for a duplicate before assigning an
address. On macOS the equivalent lookup is arp -a.
Traffic leaving through the wrong interface. Ask the kernel which route it would pick, rather than reading the table and guessing. On Linux:
$ ip route get 8.8.8.8
8.8.8.8 via 172.17.0.1 dev eth0 src 172.17.0.5 uid 0
cache macOS uses the BSD tools:
$ route -n get default | head -5
route to: default
destination: default
mask: default
gateway: 192.168.1.1
interface: en0$ netstat -nr -f inet | head -6
Routing tables
Internet:
Destination Gateway Flags Netif Expire
default 192.168.1.1 UGScg en0
default link#21 UCSIg bridge100 !A netmask that is too narrow. Two hosts that should be neighbors but cannot
reach each other, while both reach the gateway, usually means one of them has
the wrong prefix. Compare what each host believes with mask2prefix, then
confirm both resolve to the same network with in_net.
A wrong prefix rarely fails loudly. It fails for one direction, or for one pair of hosts, and everything else keeps working — which is why it is worth checking the arithmetic before rebuilding the interface.
