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.

PrefixNetmaskAddressesUsable hosts
/8255.0.0.01677721616777214
/12255.240.0.010485761048574
/16255.255.0.06553665534
/22255.255.252.010241022
/23255.255.254.0512510
/24255.255.255.0256254
/25255.255.255.128128126
/26255.255.255.1926462
/27255.255.255.2243230
/28255.255.255.2401614
/29255.255.255.24886
/30255.255.255.25242
/31255.255.255.25422
/32255.255.255.25511

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.

session
$ 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 forever

macOS has no ip command. Use ifconfig, which prints the netmask in hexadecimal rather than dotted decimal:

session
$ ifconfig en0 | grep 'inet '
	inet 192.168.1.42 netmask 0xffffff00 broadcast 192.168.1.255

0xffffff00 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:

bash
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"
}
session
$ 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 first 0 to 1 step evaluates false and triggers the break, 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.

session
$ 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 Internet

The 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:

session
$ 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.126

Neither 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:

session
$ 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:

bash
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 ))"
}
session
$ 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:

session
$ 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.254

The 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:

session
$ python3 -c "import ipaddress; print(ipaddress.ip_address('192.168.1.130') in ipaddress.ip_network('192.168.1.128/26'))"
True

In Bash, mask both addresses and compare — if the network bits match, they are in the same block:

bash
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) ))
}
session
$ 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
inside

Private 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.

RangeFirst addressLast addressAddresses
10.0.0.0/810.0.0.010.255.255.25516777216
172.16.0.0/12172.16.0.0172.31.255.2551048576
192.168.0.0/16192.168.0.0192.168.255.25565536

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:

session
$ ip neigh show
172.17.0.1 dev eth0 lladdr 02:42:36:20:6b:9e REACHABLE

arping -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:

session
$ 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:

session
$ route -n get default | head -5
   route to: default
destination: default
       mask: default
    gateway: 192.168.1.1
  interface: en0
session
$ 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.