Python · SQL · Web Dev · Java · AI/ML tracks launching soon — your one platform for all of IT

Binary, Hex & Number Systems

A complete, gap-free treatment of every number system used in networking — binary, hexadecimal, octal, and decimal — plus bitwise operations, two's complement, endianness, and how they appear in IP addresses, MAC addresses, subnets, and packet headers.

45 min

// CHAPTER 01

Why Number Systems Matter in Networking

Binary and hex are not abstract math — they are everywhere in protocols

Every network protocol is ultimately a specification of bits. IPv4 addresses are 32-bit binary numbers. MAC addresses are 48-bit hex numbers. A TCP port number is a 16-bit unsigned integer. A subnet mask is a sequence of 1s followed by 0s. VLAN tags, DSCP values, TTL fields, sequence numbers, checksum calculations — all require you to think in binary and hexadecimal fluently.

Engineers who only work in decimal are forced to convert mentally — and they make mistakes. An engineer fluent in binary and hex reads 0xFF and instantly knows it's 255, a full octet of ones, a broadcast address octet or a fully-set mask. They see /24 and immediately know the mask is 11111111.11111111.11111111.00000000 = 255.255.255.0. This chapter builds that fluency from first principles.

// REAL-WORLD SCENARIO

You're on call. A firewall ACL is blocking traffic. The rule says: permit ip 172.16.0.0 0.0.255.255. Is 172.16.48.200 matched by this rule? To answer, you need to understand that 0.0.255.255 is a wildcard mask — the inverse of a subnet mask. You AND the address with the mask and compare to the network. Getting this wrong under pressure means an outage that lasts hours instead of minutes. Binary fluency is not academic — it is a professional survival skill.

// CHAPTER 02

Positional Number Systems

How any base works — the universal framework

All positional number systems follow the same rule: the value of a digit depends on its position. Each position represents a power of the base. You already know this from decimal — you just never thought about why.

Positional value — the universal rule
Decimal (base 10):
  4,729 = 4×10³ + 7×10² + 2×10¹ + 9×10⁰
        = 4×1000 + 7×100 + 2×10 + 9×1
        = 4000 + 700 + 20 + 9 = 4,729

Binary (base 2):
  1011 = 1×2³ + 0×2² + 1×2¹ + 1×2⁰
       = 8    + 0    + 2    + 1   = 11 (decimal)

Hexadecimal (base 16):
  2F  = 2×16¹ + F×16⁰
      = 2×16  + 15×1
      = 32    + 15 = 47 (decimal)

The pattern:
  Position 0 (rightmost) = base⁰ = 1
  Position 1             = base¹ = base
  Position 2             = base² = base×base
  Position n             = baseⁿ

Every number system uses exactly this framework. Once you understand it in decimal, understanding binary (base 2) and hex (base 16) is just swapping the base. The only difference: what digits are available. Decimal has 10 digits (0–9). Binary has 2 (0–1). Hex has 16 (0–9, then A–F representing 10–15).

Powers of 2 — Memorize These

Binary powers of 2 appear constantly in networking. Memorizing at least through 2¹⁶ pays off immediately:

Powers of 2 — essential table
2⁰  = 1          2⁸  = 256         2¹⁶ = 65,536
2¹  = 2          2⁹  = 512         2¹⁷ = 131,072
2²  = 4          2¹⁰ = 1,024       2¹⁸ = 262,144
2³  = 8          2¹¹ = 2,048       2²⁴ = 16,777,216
2⁴  = 16         2¹² = 4,096       2³² = 4,294,967,296
2⁵  = 32         2¹³ = 8,192
2⁶  = 64         2¹⁴ = 16,384
2⁷  = 128        2¹⁵ = 32,768

Networking anchor points:
  2⁸  = 256  → number of values in one IPv4 octet (0–255)
  2¹⁶ = 65,536 → max TCP/UDP port number (0–65535)
  2³² = 4.3 billion → total IPv4 address space
  2⁴⁸ → total MAC address space
  2¹²⁸ → total IPv6 address space

// CHAPTER 03

Binary (Base 2)

The native language of every computer and network device

Binary is not a convenience — it is the only number system that maps directly to the physical reality of digital electronics. A transistor is either on (saturated) or off (cut off). A capacitor is either charged or discharged. Voltage is either above a threshold (1) or below it (0). Binary digit = bit (binary digit). All other representations are abstractions built on top of binary.

Converting Decimal to Binary

Two methods. The subtraction method is fastest for networking (IP addresses, subnet masks):

Subtraction method — fastest for IP addresses
Convert 192 to binary:

Powers of 2:  128  64  32  16   8   4   2   1
              2⁷   2⁶  2⁵  2⁴  2³  2²  2¹  2⁰

Step 1: 192 ≥ 128? Yes → bit 7 = 1, remainder = 192 - 128 = 64
Step 2:  64 ≥ 64?  Yes → bit 6 = 1, remainder = 64 - 64 = 0
Step 3:   0 ≥ 32?  No  → bit 5 = 0
Step 4:   0 ≥ 16?  No  → bit 4 = 0
Step 5:   0 ≥ 8?   No  → bit 3 = 0
Step 6:   0 ≥ 4?   No  → bit 2 = 0
Step 7:   0 ≥ 2?   No  → bit 1 = 0
Step 8:   0 ≥ 1?   No  → bit 0 = 0

192 = 11000000

Verify: 128 + 64 = 192 ✓
Division method — systematic, good for any number
Convert 172 to binary (divide by 2, track remainders):

172 ÷ 2 = 86  remainder 0  ← LSB (bit 0)
 86 ÷ 2 = 43  remainder 0
 43 ÷ 2 = 21  remainder 1
 21 ÷ 2 = 10  remainder 1
 10 ÷ 2 =  5  remainder 0
  5 ÷ 2 =  2  remainder 1
  2 ÷ 2 =  1  remainder 0
  1 ÷ 2 =  0  remainder 1  ← MSB (bit 7)

Read remainders bottom to top: 10101100
172 = 10101100

Verify: 128 + 0 + 32 + 0 + 8 + 4 + 0 + 0 = 172 ✓

Converting Binary to Decimal

Write down the bit values for each position, multiply each bit by its position value, sum the results:

Binary to decimal
Binary:   1  1  0  1  0  1  0  0
Position: 7  6  5  4  3  2  1  0
Value:   128 64 32 16  8  4  2  1

Bits set: positions 7, 6, 4, 2 → 128 + 64 + 16 + 4 = 212

Shortcut for memorized subnet masks:
  11111111 = 128+64+32+16+8+4+2+1 = 255
  11111110 = 255 - 1 = 254
  11111100 = 255 - 3 = 252
  11111000 = 255 - 7 = 248
  11110000 = 255 - 15 = 240
  11100000 = 255 - 31 = 224
  11000000 = 255 - 63 = 192
  10000000 = 255 - 127 = 128

Bits, Nibbles, Bytes, and Words

Bit: One binary digit (0 or 1). The fundamental unit.

Nibble: 4 bits. Can represent values 0–15. Exactly one hex digit. Why nibbles matter: you split a byte into two nibbles to convert to hex. A VLAN ID (12 bits) is three nibbles.

Byte (octet): 8 bits. Can represent 256 values (0–255). One IPv4 address octet. One ASCII character. In networking, "octet" is technically more precise than "byte" because "byte" historically meant variable bit counts (5, 6, 7, or 8 bits) — the term "octet" always means exactly 8 bits.

Word: Platform-dependent — historically 16 bits on 16-bit systems, 32 bits on 32-bit, 64 bits on 64-bit. In networking, "word" usually means 32 bits (IPv4 header fields are measured in 32-bit words — the IHL field says "header length in 32-bit words").

Size hierarchy
1 bit      = 0 or 1
4 bits     = 1 nibble = 1 hex digit  (0–15, 0x0–0xF)
8 bits     = 1 byte/octet            (0–255, 0x00–0xFF)
16 bits    = 2 bytes                 (0–65535, used for ports)
32 bits    = 4 bytes                 (IPv4 address, TCP seq number)
48 bits    = 6 bytes                 (MAC address)
64 bits    = 8 bytes                 (IPv6 half-address)
128 bits   = 16 bytes                (IPv6 full address, UUID)

Common confusions:
  "Kbps" vs "KBps": lowercase b = bits, uppercase B = bytes
  1 Mbps download = 1,000,000 bits/s ÷ 8 = 125,000 bytes/s = 125 KB/s
  Your "100 Mbps" ISP link: max file download speed ≈ 12.5 MB/s

// CHAPTER 04

Hexadecimal (Base 16)

The compact notation for binary data

Hexadecimal exists for one reason: binary is hard to read for humans. A 48-bit MAC address in binary is 48 characters of 0s and 1s — impossible to parse at a glance. In hex, the same address is 12 characters: A4:C3:F0:85:AC:2B. Every group of 4 binary bits maps to exactly one hex digit. This makes hex the natural compression format for binary data.

Hex Digits

Hex digit table — the full mapping
Decimal  Binary  Hex    Decimal  Binary  Hex
   0     0000    0         8     1000    8
   1     0001    1         9     1001    9
   2     0010    2        10     1010    A
   3     0011    3        11     1011    B
   4     0100    4        12     1100    C
   5     0101    5        13     1101    D
   6     0110    6        14     1110    E
   7     0111    7        15     1111    F

Prefix conventions:
  C/Python/Wireshark:   0xFF, 0xC0A80101
  Assembly:             FFh
  HTML/CSS color codes: #FF6600
  Network (MAC/IPv6):   A4:C3:F0:85:AC:2B or A4-C3-F0-85-AC-2B

Binary ↔ Hex: The 4-Bit Shortcut

Because 16 = 2⁴, every group of exactly 4 binary bits corresponds to exactly one hex digit. This makes conversion trivial — no arithmetic needed:

Binary ↔ Hex via nibble grouping
Binary to hex — split into groups of 4 from the right:
  11000000.10101000.00000001.00000001  (192.168.1.1)

  Split each octet: 1100 0000 | 1010 1000 | 0000 0001 | 0000 0001
  Look up each:       C    0  |  A    8   |  0    1   |  0    1
  Result: C0.A8.01.01 = 0xC0A80101

Hex to binary — expand each hex digit to 4 bits:
  0xDEADBEEF
  D    E    A    D    B    E    E    F
  1101 1110 1010 1101 1011 1110 1110 1111

MAC address 00:1A:2B:3C:4D:5E:
  00   = 0000 0000
  1A   = 0001 1010
  2B   = 0010 1011
  3C   = 0011 1100
  4D   = 0100 1101
  5E   = 0101 1110

Hex in Networking — Where You See It

MAC addresses: 48-bit addresses written as 6 hex octets: A4:C3:F0:85:AC:2B. The first 3 octets (OUI — Organizationally Unique Identifier) identify the manufacturer. Wireshark resolves OUIs to vendor names.

IPv6 addresses: 128-bit addresses written as 8 groups of 4 hex digits: 2001:0db8:85a3:0000:0000:8a2e:0370:7334. Each group is 16 bits = 4 hex digits. Consecutive groups of all zeros can be compressed with ::.

Ethernet EtherType field: 2-byte hex code in every Ethernet frame header identifying the Layer 3 protocol: 0x0800 = IPv4, 0x0806 = ARP, 0x86DD = IPv6, 0x8100 = VLAN (802.1Q).

Packet captures (Wireshark/tcpdump): The hex dump view shows raw packet bytes in hex pairs. Reading a packet header directly requires converting hex fields to understand protocol values.

Cryptography and TLS: Keys, IVs, hashes, certificate fingerprints — all expressed in hex. A SHA-256 hash is 256 bits = 32 bytes = 64 hex characters.

// NUMBER BASE CONVERTER

Input value

From base

Binary (base 2) 0b

0b11000000

digits: 0–1

Octal (base 8) 0o

0o300

digits: 0–7

Decimal (base 10)

192

digits: 0–9

Hex (base 16) 0x

0xC0

digits: 0–9,A–F

Binary bit groups (nibbles)

1100

C

0000

0

Each group of 4 binary digits = 1 hex digit. Decimal value: 192

// CHAPTER 05

Octal (Base 8)

Less common today, but still appears in Unix file permissions

Octal (base 8) uses digits 0–7. Each octal digit represents exactly 3 binary bits. Octal was more common when computers had 6-bit, 12-bit, or 36-bit architectures. In modern networking, octal is rare — but you'll encounter it in one critical context: Unix/Linux file permissions.

Octal in Unix file permissions
ls -la /etc/passwd
-rw-r--r-- 1 root root 2048 May 26 /etc/passwd

Permission bits: rw- r-- r--
Each group of 3 bits → one octal digit:

  Owner:  rw- = 110 = 6
  Group:  r-- = 100 = 4
  Other:  r-- = 100 = 4

chmod 644 /etc/passwd  ← octal 644 = owner rw, group r, other r
chmod 755 /etc/script  ← 7=rwx owner, 5=r-x group, 5=r-x other
chmod 600 ~/.ssh/id_rsa ← 6=rw owner only (SSH requires this)

3 bits per position × 3 positions = 9 permission bits
Plus 3 special bits (setuid, setgid, sticky) → full mode is 12 bits / 4 octal digits
  chmod 4755 /usr/bin/sudo ← setuid (4) + owner rwx (7) + group r-x (5) + other r-x (5)

Binary ↔ Octal

Group binary bits in sets of 3 (from the right) to convert to octal — each group of 3 bits = one octal digit:

Binary ↔ Octal
Binary: 1 1 0 1 0 1 0 0
Group in 3s from right: 11 | 010 | 100
  → 011 | 010 | 100   (pad leftmost group to 3 bits)
  →   3     2     4
Octal: 324

Octal to decimal: 3×64 + 2×8 + 4×1 = 192 + 16 + 4 = 212
Decimal 212 → binary 11010100 → octal 324 ✓

// CHAPTER 06

Number System Conversions — Complete Reference

Every conversion path, with worked examples

The converter below lets you practice all conversions interactively. The conceptual map of all conversion paths:

Conversion paths summary
  Decimal ←→ Binary:   division-by-2 method / positional sum
  Decimal ←→ Hex:      division-by-16 method / positional sum
    (or: Decimal → Binary → Hex via nibble grouping)
  Binary  ←→ Hex:      group 4 bits = 1 hex digit (fastest)
  Binary  ←→ Octal:    group 3 bits = 1 octal digit
  Hex     ←→ Octal:    Hex → Binary → Octal (no direct shortcut)

Fast mental conversions for networking:
  /8  mask  = 255.0.0.0      = 0xFF000000 = 11111111.00000000.00000000.00000000
  /16 mask  = 255.255.0.0    = 0xFFFF0000
  /24 mask  = 255.255.255.0  = 0xFFFFFF00
  /25 mask  = 255.255.255.128 = 0xFFFFFF80 (10000000)
  /26 mask  = 255.255.255.192 = 0xFFFFFFC0 (11000000)
  /27 mask  = 255.255.255.224 = 0xFFFFFFE0 (11100000)
  /28 mask  = 255.255.255.240 = 0xFFFFFFF0 (11110000)
  /29 mask  = 255.255.255.248 = 0xFFFFFFF8 (11111000)
  /30 mask  = 255.255.255.252 = 0xFFFFFFFC (11111100)
  /31 mask  = 255.255.255.254 = 0xFFFFFFFE (11111110)
  /32 mask  = 255.255.255.255 = 0xFFFFFFFF (all 1s)

// IPv4 ADDRESS BINARY BREAKDOWN

IP Address

OctetDecimalBinary (8 bits)Hex
Octet 1192
11000000
0xC0
Octet 2168
10101000
0xA8
Octet 31
00000001
0x01
Octet 41
00000001
0x01

Full 32-bit binary representation

11000000.10101000.00000001.00000001

Hex: C0:A8:01:01  |  Decimal: 3232235777

// CHAPTER 07

Signed Numbers and Two's Complement

How negative numbers work in binary — and why it matters for protocol fields

Networking protocol fields are mostly unsigned integers (port numbers, TTL, sequence numbers are always non-negative). But understanding signed binary arithmetic matters when: reading C code that parses headers, understanding TCP sequence number wraparound, analyzing signed vs unsigned overflow vulnerabilities, and working with routing metrics that can be negative.

Sign-Magnitude (naive approach — not used)

The obvious idea: use the most-significant bit as a sign bit (0 = positive, 1 = negative), with the remaining bits as the magnitude. Problem: two representations of zero (+0 = 00000000, -0 = 10000000), and subtraction circuits don't simplify. This approach was used in very early computers but abandoned.

Two's Complement (universal standard)

In two's complement, the MSB has a negative positional value: for an 8-bit signed integer, bit 7 = -128 instead of +128. All other bits retain their normal positive values.

Two's complement — signed 8-bit range
Bit pattern   Unsigned value   Signed (two's complement)
00000000           0                  0
00000001           1                  1
01111111          127                127   ← max positive
10000000          128               -128   ← MSB = -128
10000001          129               -127
11111110          254                 -2
11111111          255                 -1

Value formula: -(bit7 × 128) + (bit6 × 64) + ... + (bit0 × 1)

For 10000001:
  -(1 × 128) + (0×64) + (0×32) + (0×16) + (0×8) + (0×4) + (0×2) + (1×1)
  = -128 + 1 = -127 ✓

Computing Two's Complement (negating a number)

To negate a number in two's complement: invert all bits, then add 1.

Two's complement negation
Negate +45 (00101101) to get -45:

Step 1 — Invert all bits:  00101101 → 11010010
Step 2 — Add 1:            11010010 + 00000001 = 11010011

Verify: 11010011 = -(1×128) + (1×64) + (0×32) + (1×16) + (0×8) + (0×4) + (1×2) + (1×1)
                 = -128 + 64 + 16 + 2 + 1 = -45 ✓

Practical application — One's complement in checksums:
  The Internet Checksum (TCP, UDP, IPv4 headers) uses one's complement addition
  One's complement = just invert all bits (no +1)
  The checksum is designed so that summing all words + checksum = all 1s (0xFFFF)
  At receiver: if sum ≠ 0xFFFF, the header is corrupted

Integer Overflow and TCP Sequence Numbers

TCP sequence numbers are 32-bit unsigned integers (0 to 4,294,967,295). They wrap around: after 4,294,967,295, the next sequence number is 0. TCP is designed to handle this correctly using modular arithmetic comparisons — SEQ_GT(a, b) uses (a - b) > 0 with unsigned arithmetic to handle wraparound.

On a 10 Gbps link transferring data at full speed: 10 Gbps = 1.25 GB/s. 4 GB / 1.25 GB/s = 3.2 seconds to cycle through all sequence numbers. The TCP Protection Against Wrapped Sequences (PAWS) extension uses timestamps to disambiguate wrapped-around sequence numbers.

// CHAPTER 08

Bitwise Operations in Networking

AND, OR, XOR, NOT, shifts — the operations behind subnetting and headers

Bitwise operations work on individual bits of a number. They are not abstract algebra — they appear constantly in real protocol processing: subnet calculation, header field extraction, flag setting/testing, and checksum computation.

// BITWISE OPERATIONS VISUALIZER

A (0–255)

B (0–255)

A = 192
11000000
0xC0
B = 255
11111111
0xFF
────── & ──────
Result = 192
11000000
0xC0

Both bits must be 1 → 1. Used for masking (extracting specific bits). Subnet masking is AND.

AND — Subnet Masking

Bitwise AND is the operation behind every subnet calculation. To find the network address of an IP address: Network = IP AND Subnet Mask

Subnet masking with AND
IP address:    192.168.10.45   = 11000000.10101000.00001010.00101101
Subnet mask:   255.255.255.0   = 11111111.11111111.11111111.00000000
                                 ────────────────────────────────────── AND
Network addr:  192.168.10.0    = 11000000.10101000.00001010.00000000

Rule: wherever the mask bit is 1 → keep the IP bit
      wherever the mask bit is 0 → force bit to 0
The result is the network address.

For /26 subnet (255.255.255.192 = ...11000000):
IP:    192.168.1.100 = ...01100100
Mask:  255.255.255.192 = ...11000000
                         ─────────── AND
Network: 192.168.1.64  = ...01000000

OR — Setting Bits

OR sets specific bits to 1 without affecting others. Used to: calculate broadcast addresses, set flag fields in headers, construct IP addresses from network+host portions.

Broadcast address with OR
Broadcast = Network OR Wildcard_Mask
Wildcard mask = bitwise NOT of subnet mask

Network:  192.168.10.0   = ...00001010.00000000
Wildcard: 0.0.0.255      = ...00000000.11111111
                           ──────────────────── OR
Broadcast:192.168.10.255 = ...00001010.11111111

Setting the ACK flag in a TCP flags byte:
  current_flags = 0x02  (00000010 = SYN set)
  ACK_bit = 0x10        (00010000 = ACK position)
  new_flags = 0x02 | 0x10 = 0x12 (00010010 = SYN+ACK)

XOR — Checksums and Encryption

XOR has a unique property: A XOR B XOR B = A. XORing with the same value twice gives back the original. This makes XOR the foundation of stream ciphers (one-time pad: ciphertext = plaintext XOR key; recover plaintext = ciphertext XOR key). It's also used in RAID-5/6 parity: Parity = Block1 XOR Block2 XOR Block3. If one block is lost, it's recovered by XOR of the remaining blocks with parity.

XOR properties — networking uses
A XOR A = 0      (anything XORed with itself = 0)
A XOR 0 = A      (XOR with 0 = identity)
A XOR B = B XOR A (commutative)

CRC computation (simplified): treat data as polynomial, XOR operations
  throughout — CRC-32 = polynomial division using XOR, no borrowing

Wireshark highlights XOR in VXLAN/GENEVE UDP source port selection:
  Source port = hash of inner L2/L3/L4 headers → distribute across ECMP paths
  Hash typically uses XOR of source+dest IPs and ports

NOT — Inverting Masks

Bitwise NOT flips every bit. In networking: Wildcard mask = NOT(Subnet mask). Cisco ACLs use wildcard masks (inverted subnet masks). A wildcard mask of 0.0.0.255 means "match any value in the last octet."

Wildcard mask = NOT subnet mask
Subnet mask:   255.255.255.0 = 11111111.11111111.11111111.00000000
NOT:                           00000000.00000000.00000000.11111111
Wildcard mask: 0.0.0.255

ACL rule: permit ip 10.0.0.0 0.255.255.255
  → match any IP with first octet = 10 (10.0.0.0/8)

Rule: in wildcard masks, 0 = must match, 1 = don't care
  (opposite of subnet mask: 1 = match, 0 = don't care)

Bit Shifts — Extracting Header Fields

Left shift (<<) multiplies by 2ⁿ. Right shift (>>) divides by 2ⁿ. In networking, shifts extract or insert multi-bit fields within a byte or word:

Bit shifts — reading packet header fields
IPv4 header first byte:
  Version (bits 7-4) | IHL (bits 3-0)

Read version from first byte (0x45 = 0100 0101):
  version = (0x45 >> 4) = 0x04 = 4    ← shift right 4, keep upper nibble
  ihl     = (0x45 & 0x0F) = 0x05 = 5  ← AND with 0x0F, keep lower nibble
  header_length_bytes = ihl × 4 = 20 bytes

Constructing a byte with two 4-bit fields:
  version = 4  (0100)
  ihl     = 5  (0101)
  first_byte = (version << 4) | ihl
             = (0x04 << 4) | 0x05
             = 0x40 | 0x05 = 0x45 ✓

DSCP field extraction from IPv4 ToS byte:
  tos_byte = 0xB8 (1011 1000) = EF (Expedited Forwarding)
  dscp = tos_byte >> 2       = 0x2E = 46 (DSCP EF)
  ecn  = tos_byte & 0x03     = 0x00 (no ECN)

✗ Common Mistake — Forgetting operator precedence in bitwise expressions

In C and most languages, x & 0x0F == 0 is evaluated as x & (0x0F == 0) — not (x & 0x0F) == 0 — because == has higher precedence than &. This is a real source of bugs in packet parsing code. Always use explicit parentheses: (x & 0x0F) == 0.

// CHAPTER 09

Binary in IP Addressing and Subnetting

Binary thinking is the only way to truly understand IP

IPv4 addresses and subnet masks only make sense in binary. Decimal notation (192.168.1.0/24) is a human-readable convenience — every routing and forwarding decision is made in binary.

CIDR Prefix Length

A CIDR prefix /N means the first N bits are the network portion and the remaining (32-N) bits are the host portion. The subnet mask is simply N consecutive 1s followed by (32-N) zeros:

CIDR prefix to subnet mask
/24 → 24 ones, 8 zeros:
  11111111 11111111 11111111 00000000 = 255.255.255.0
  Hosts: 2⁸ - 2 = 254 (subtract network and broadcast)

/26 → 26 ones, 6 zeros:
  11111111 11111111 11111111 11000000 = 255.255.255.192
  Hosts: 2⁶ - 2 = 62

/30 → 30 ones, 2 zeros:
  11111111 11111111 11111111 11111100 = 255.255.255.252
  Hosts: 2² - 2 = 2  (point-to-point links use /30 or /31)

/31 → 31 ones, 1 zero:
  11111111 11111111 11111111 11111110 = 255.255.255.254
  RFC 3021: /31 allows 2 usable addresses (no network/broadcast — point-to-point)

/32 → all 32 ones:
  11111111 11111111 11111111 11111111 = 255.255.255.255
  Host route — exactly one specific address

Subnetting Step by Step (Binary Method)

Given: network 192.168.1.0/24, need 4 equal subnets. Each subnet borrows 2 bits (2² = 4 subnets):

Subnetting in binary
Original: 192.168.1.0/24 = 192.168.1.  00000000
Borrow 2 host bits → /26 = 192.168.1.  XX000000

Subnet 00: 192.168.1.00000000 = 192.168.1.0/26   (hosts: .1–.62)
Subnet 01: 192.168.1.01000000 = 192.168.1.64/26  (hosts: .65–.126)
Subnet 10: 192.168.1.10000000 = 192.168.1.128/26 (hosts: .129–.190)
Subnet 11: 192.168.1.11000000 = 192.168.1.192/26 (hosts: .193–.254)

Network increment = 2^(host bits) = 2^6 = 64
Verify: 0, 64, 128, 192 — each subnet starts 64 apart ✓

Broadcast of each subnet = next subnet address - 1:
  .0/26 broadcast = .63
  .64/26 broadcast = .127
  .128/26 broadcast = .191
  .192/26 broadcast = .255

Is This Host in This Subnet? (Binary Test)

To determine if two addresses are in the same subnet, AND both with the subnet mask and compare the results:

Subnet membership test
Is 192.168.1.100 in subnet 192.168.1.64/26?

Host:   192.168.1.100 = ...01100100
Mask:   255.255.255.192 = ...11000000
                          ──────────── AND
Result: 192.168.1.64  = ...01000000  ← network address

Subnet: 192.168.1.64
Match? 192.168.1.64 == 192.168.1.64 → YES ✓

Is 192.168.1.200 in subnet 192.168.1.64/26?
Host:   ...11001000
Mask:   ...11000000
                    AND
Result: ...11000000 = 192.168.1.192 ≠ 192.168.1.64 → NO ✗

🔢 IPv6 addresses and hex

IPv6 addresses are 128 bits, written as 8 groups of 4 hex digits (32 hex chars total). 2001:0db8:85a3:0000:0000:8a2e:0370:7334. Two compression rules: (1) leading zeros in each group can be omitted: 2001:db8:85a3:0:0:8a2e:370:7334; (2) one consecutive run of all-zero groups can be replaced with ::: 2001:db8:85a3::8a2e:370:7334. The :: can only appear once. IPv6 prefix length works the same as IPv4: /64 means the first 64 bits are the network prefix.

// CHAPTER 10

Endianness

Byte order — why the same number looks different on different machines

When a multi-byte number (like a 32-bit IP address or a 16-bit port number) is stored in memory or transmitted over a network, the bytes must be placed in some order. Endianness is the convention that determines which byte comes first.

Big-Endian vs Little-Endian

Big-endian (network byte order): the most significant byte (MSB) is stored at the lowest memory address / sent first on the wire. Storing the 32-bit value 0x12345678 in memory starting at address 0x1000:

Big-endian vs Little-endian storage
Value: 0x12345678 (decimal: 305,419,896)
Bytes: [0x12] [0x34] [0x56] [0x78]

Big-endian (network byte order):
  Address: 0x1000  0x1001  0x1002  0x1003
  Byte:      0x12    0x34    0x56    0x78
  MSB first (most significant byte at lowest address)

Little-endian (x86/x86-64, ARM in default mode):
  Address: 0x1000  0x1001  0x1002  0x1003
  Byte:      0x78    0x56    0x34    0x12
  LSB first (least significant byte at lowest address)

Networks always use big-endian — this is why it's called "network byte order".
All protocol field values in packet headers are big-endian.

Why It Matters for Networking Code

x86/x86-64 CPUs (virtually all PCs and servers) are little-endian. Network protocols are big-endian. Every time you read a multi-byte field from a packet in C code, you must convert:

Byte order conversion in C
#include <arpa/inet.h>

// Network to host (big-endian → CPU native endian)
uint16_t port = ntohs(tcp_header->dest_port);   // 16-bit
uint32_t addr = ntohl(ip_header->dest_addr);     // 32-bit

// Host to network (CPU native → big-endian for sending)
tcp_header->dest_port = htons(443);              // 16-bit
ip_header->dest_addr  = htonl(0xC0A80101);      // 32-bit

// On big-endian systems (SPARC, MIPS, some ARM):
//   ntohs/htons are no-ops — no byte swap needed
// On little-endian (x86/x86-64):
//   these functions reverse the bytes

Python equivalent (using struct module):
  import struct
  port = struct.unpack('!H', data[0:2])[0]  # '!' = network byte order
  addr = struct.unpack('!I', data[0:4])[0]  # 'I' = unsigned 32-bit int

Wireshark and Endianness

Wireshark handles byte-order conversion transparently — it reads raw network (big-endian) bytes and displays them as human-readable decimal or hex values. When you see a TCP port of 443 in Wireshark, the actual bytes in the packet are 0x01 0xBB (big-endian: 0×256 + 187 = 443). If you look at the raw hex dump in Wireshark's packet bytes panel, you'll see these raw bytes.

Endianness trap in protocol parsing

A common bug: reading a 16-bit port number without byte-swap conversion on a little-endian machine. Port 443 (0x01BB) would be read as 0xBB01 = 47873 — completely wrong. Always use htons/ntohs (C) or struct.pack with '!' prefix (Python) for any multi-byte field read from a packet.

// CHAPTER 11

Binary in Packet Headers

Reading real protocol headers in hex and binary

Everything you've learned now combines. Let's read a real IPv4 header byte by byte, extracting every field in binary and hex.

IPv4 Header — Binary Field Extraction

Real IPv4 header hex dump → field parsing
Raw hex bytes (first 20 bytes = minimum IPv4 header):
45 00 00 3c 1a 46 40 00 40 06 a6 ec c0 a8 01 01 c0 a8 01 02

Byte-by-byte breakdown:
  0x45 → Version=4 (0100), IHL=5 (0101) → header=20 bytes
  0x00 → DSCP=0 (best effort), ECN=0
  0x00 0x3c → Total Length = 0x003C = 60 bytes
  0x1a 0x46 → Identification = 0x1A46 = 6726
  0x40 0x00 → Flags=010 (DF set, no MF), Fragment Offset=0
              0x40 = 0100 0000 → bit 6 set = DF (Don't Fragment)
  0x40 → TTL = 64 hops
  0x06 → Protocol = 6 = TCP
  0xa6 0xec → Header Checksum = 0xA6EC
  0xc0 0xa8 0x01 0x01 → Src IP = 192.168.1.1
  0xc0 0xa8 0x01 0x02 → Dst IP = 192.168.1.2

Flags field (3 bits): 0x40 >> 5 = 010
  bit 0 (MSB): reserved = 0
  bit 1: DF (Don't Fragment) = 1 ← set
  bit 2: MF (More Fragments) = 0

TCP Header — Binary Field Extraction

TCP header hex dump → field parsing
TCP header start (20 bytes minimum):
c8 f6 01 bb 00 00 00 01 00 00 00 00 a0 02 ff ff ...

  0xc8 0xf6 → Source Port = 0xC8F6 = 51446
  0x01 0xbb → Dest Port   = 0x01BB = 443  (HTTPS)
  0x00 0x00 0x00 0x01 → Sequence Number = 1
  0x00 0x00 0x00 0x00 → Acknowledgment Number = 0
  0xa0 → Data Offset = 0xA >> = 10 (in nibble: 1010 → 10 words = 40 bytes header)
          (0xa0 = 1010 0000 → upper nibble 1010 = 10, lower nibble 0000 = reserved)
  0x02 → Flags byte = 0000 0010 = SYN set
          bit 0 (FIN) = 0
          bit 1 (SYN) = 1 ← SYN packet
          bit 2 (RST) = 0
          bit 3 (PSH) = 0
          bit 4 (ACK) = 0
          bit 5 (URG) = 0
  0xff 0xff → Window Size = 65535 bytes

MAC Address OUI Lookup

MAC address breakdown
MAC: A4:C3:F0:85:AC:2B

Bytes:  A4   C3   F0   85   AC   2B
Hex:   0xA4 0xC3 0xF0 0x85 0xAC 0x2B
Bin:  10100100 11000011 11110000 10000101 10101100 00101011

First byte (0xA4 = 10100100):
  bit 0 (LSB): multicast bit = 0 → unicast address
  bit 1:       locally administered bit = 0 → globally unique (OUI-assigned)
  bits 7-2:    part of OUI

OUI (first 3 bytes): A4:C3:F0 = Apple, Inc.

Special MAC addresses:
  FF:FF:FF:FF:FF:FF = broadcast (all bits 1)
  01:00:5E:xx:xx:xx = IPv4 multicast (first 3 bytes fixed, last 23 bits = low 23 bits of multicast group)
  33:33:xx:xx:xx:xx = IPv6 multicast (first 2 bytes = 0x3333)
  00:00:00:00:00:00 = unset/invalid

// CHAPTER 12

Practical Tools for Binary and Hex

Working with binary in the real world

Theory is only useful if you can apply it at the command line, in code, and in protocol analysis tools.

Command-Line Number Conversions

Shell and Python conversions
# Bash — printf for base conversion
printf "%d\n" 0xFF         # hex to decimal: 255
printf "%x\n" 255          # decimal to hex: ff
printf "%o\n" 255          # decimal to octal: 377
printf "%08b\n" 192        # decimal to binary (zsh/bash with printf %b): 11000000

# Python — most flexible
>>> bin(192)        # '0b11000000'
>>> hex(192)        # '0xc0'
>>> oct(192)        # '0o300'
>>> int('C0', 16)   # 192  ← hex string to int
>>> int('11000000', 2)  # 192  ← binary string to int
>>> format(192, '08b')  # '11000000'  ← zero-padded binary
>>> format(192, '02x')  # 'c0'  ← zero-padded hex

# Python socket library for IP address binary
>>> import socket, struct
>>> socket.inet_aton('192.168.1.1')        # b'\xc0\xa8\x01\x01'
>>> socket.inet_ntoa(b'\xc0\xa8\x01\x01')  # '192.168.1.1'

# Python struct for network byte order
>>> import struct
>>> struct.pack('!H', 443)           # b'\x01\xbb'  (big-endian port 443)
>>> struct.unpack('!H', b'\x01\xbb') # (443,)

ipcalc — Subnet Calculator

ipcalc output
$ ipcalc 192.168.10.45/26

Address:   192.168.10.45        11000000.10101000.00001010. 00101101
Netmask:   255.255.255.192 = 26  11111111.11111111.11111111. 11000000
Wildcard:  0.0.0.63             00000000.00000000.00000000. 00111111
Network:   192.168.10.0/26      11000000.10101000.00001010. 00000000
HostMin:   192.168.10.1         11000000.10101000.00001010. 00000001
HostMax:   192.168.10.62        11000000.10101000.00001010. 00111110
Broadcast: 192.168.10.63        11000000.10101000.00001010. 00111111
Hosts/Net: 62                   Class C, Private Internet

Wireshark Hex Dump

In Wireshark: select any packet → bottom panel shows raw bytes. Left column = hex pairs, right column = ASCII interpretation (dots for non-printable bytes). Clicking on a field in the middle panel highlights the corresponding hex bytes at the bottom. This is how you verify your binary/hex understanding against real traffic.

tcpdump hex output
# Capture and display hex+ascii dump
tcpdump -XX -i eth0 -n host 192.168.1.1

# Output example (first packet):
IP 192.168.1.1 > 192.168.1.2: ICMP echo request
    0x0000:  4500 003c 1a46 4000 4001 a6f0 c0a8 0101  E..<.F@.@.......
    0x0010:  c0a8 0102 0800 ...                        ................
             ────────────────
             c0a8 0101 = 192.168.1.1  (source IP in hex, big-endian)
             c0a8 0102 = 192.168.1.2  (dest IP in hex, big-endian)

// CHAPTER 13

Interview Questions

From beginner to PhD

Beginner

Convert 172.16.5.0/22 to binary. How many hosts does this subnet support?

172.16.5.0 in binary: 10101100.00010000.00000101.00000000. /22 means 22 bits of network, 10 bits of host. Hosts = 2¹⁰ - 2 = 1022. The subnet covers 172.16.4.0 through 172.16.7.255 (4 × /24 blocks).
Beginner

What is 0xC0A80101? What is its significance in networking?

0xC0 = 192, 0xA8 = 168, 0x01 = 1, 0x01 = 1 → 192.168.1.1. This is a private IPv4 address (RFC 1918 range 192.168.0.0/16), commonly used as a default gateway address on home/office routers.
Intermediate

A router receives a packet for 10.10.15.200. Its routing table has 10.10.0.0/16 and 10.10.15.0/24. Which route wins and why?

10.10.15.0/24 wins — longest prefix match. 10.10.15.200 AND 255.255.255.0 = 10.10.15.0 ✓ (matches /24). 10.10.15.200 AND 255.255.0.0 = 10.10.0.0 ✓ (also matches /16). When multiple routes match, routers always use the most specific (longest prefix) match. This is fundamental to IP routing and enables hierarchical aggregation without losing reachability to specific subnets.
Intermediate

Explain the TCP flags byte: what does 0x12 mean, and how do you extract individual flags using bitwise operations?

0x12 = 0001 0010 in binary. Bit 1 (SYN) = 1, bit 4 (ACK) = 1 → SYN-ACK packet (the second message in the TCP 3-way handshake). Extract flags: syn = (flags >> 1) & 1, ack = (flags >> 4) & 1. Or test: is_ack = (flags & 0x10) != 0.
Senior

What is network byte order? Why does it exist, and what happens if you forget to convert in a protocol parser?

Network byte order is big-endian (MSB first), standardized in RFC 791. It exists because different CPU architectures store multi-byte integers in different orders (x86 is little-endian, SPARC is big-endian). Without a standard, a port number written as 0x01BB by one machine would be read as 0xBB01 by another. Forgetting to convert: a 16-bit port 443 (0x01BB) is read as 47873 on a little-endian machine — a port that's almost certainly closed, causing silent connection failures that are hard to debug. In C, always use ntohs()/ntohl() for reading and htons()/htonl() for writing. In Python, use struct with '!' prefix.
PhD

Explain how IPv6 address compression works. What is the full expansion of 2001:db8::1? Design an algorithm to compress an IPv6 address.

Full expansion of 2001:db8::1: the :: replaces all consecutive zero groups needed to make 8 total. 2001:db8 is 2 groups → 5 zero groups needed → 2001:0db8:0000:0000:0000:0000:0000:0001.

Compression algorithm: (1) Split address into 8 16-bit groups. (2) Find all runs of consecutive zero groups. (3) Replace the longest run with :: (if tie, replace the first). (4) Within each remaining group, remove leading zeros. RFC 5952 adds tie-breaking rules: if equal-length zero runs exist, replace the first one; the compressed form must be canonical (lowercase hex). Edge cases: 0:0:0:0:0:0:0:1::1 (loopback); 0:0:0:0:0:0:0:0:: (unspecified); IPv4-mapped: 0:0:0:0:0:FFFF:C0A8:0101::ffff:192.168.1.1.

🎯 Key Takeaways

  • All positional number systems follow the same rule: digit value × base^position. Binary (base 2) and hex (base 16) are just different bases of the same framework.
  • Binary is the native language of computers — every IP address, MAC address, port number, and header field is ultimately a binary number. Decimal and hex are human conveniences.
  • Each hex digit represents exactly 4 binary bits (a nibble). This makes hex ↔ binary conversion trivial — group bits in sets of 4 and look up the table.
  • Memorize powers of 2 through 2¹⁶: 2⁸=256 (IPv4 octet range), 2¹⁶=65536 (port numbers), 2³²=4.3 billion (IPv4 address space).
  • Two's complement is the universal standard for signed integers: negate by inverting all bits then adding 1. MSB has a negative weight (-128 for 8-bit).
  • AND is subnet masking (network = IP & mask). OR sets bits (broadcast = network | wildcard). XOR is used in CRC, parity, and encryption. NOT inverts masks (wildcard = NOT mask).
  • CIDR prefix /N means N bits of network, (32-N) bits of host. 2^(32-N) - 2 = usable hosts. Subnet boundaries are always multiples of 2^(32-N).
  • Network byte order is big-endian (MSB first). x86/x86-64 CPUs are little-endian. Always use ntohs/ntohl (C) or struct with "!" (Python) when reading multi-byte fields from packets.
  • IPv6 uses 128-bit hex addresses. Leading zeros in each group and consecutive all-zero groups (::) can be compressed. :: can only appear once per address.
  • Bitwise operations on header fields: shift right to extract upper nibble (version), AND with mask to extract lower nibble (IHL), shift left to reconstruct. Always parenthesize bitwise expressions.
  • ipcalc, Python struct/socket, and Wireshark hex dump are the practical tools for binary/hex work. Wireshark shows raw hex with field highlighting for protocol learning.
Share

Discussion

0

Have a better approach? Found something outdated? Share it — your knowledge helps everyone learning here.

Continue with GitHub
Loading...