Ethernet, Frames & MAC Addresses
By the end of this lesson you will be able to draw an Ethernet frame field by field, read a MAC address and tell unicast from broadcast, explain bit stuffing, and calculate the minimum frame size for CSMA/CD.
The physical layer only moves a stream of bits. It has no idea where one message ends and the next begins. The data link layer fixes that by grouping bits into frames, giving each frame a source and destination address, and checking it for damage. Ethernet is by far the most common data link protocol in wired networks.
Framing: where does a frame start and end?
Imagine Isha reading a long telegram with no full stops. She cannot tell where one sentence ends. A receiver faces the same problem with a stream of bits. There are three common answers:
- A length field. The header says how many bytes follow.
- Byte stuffing. A special flag byte marks the start and end. If the flag byte appears inside the data, an escape byte is put before it.
- Bit stuffing. A special bit pattern,
01111110, marks the start and end. To stop this pattern appearing inside the data, the sender inserts a0after every five1s in a row. The receiver removes it.
Bit stuffing is an exam favourite, so let us run it. Step 1: walk through the bits counting consecutive ones. Step 2: after five ones, add a zero and reset the count.
def stuff(bits):
out, ones = "", 0
for b in bits:
out += b
ones = ones + 1 if b == "1" else 0
if ones == 5:
out += "0"
ones = 0
return out
def unstuff(bits):
out, ones, skip = "", 0, False
for b in bits:
if skip:
skip = False
ones = 0
continue
out += b
ones = ones + 1 if b == "1" else 0
if ones == 5:
skip = True
return out
data = "0111111101111110"
sent = stuff(data)
print("Data: ", data)
print("Stuffed: ", sent)
print("Restored:", unstuff(sent), unstuff(sent) == data)Data: 0111111101111110 Stuffed: 011111011011111010 Restored: 0111111101111110 True
Two zeros were added, one after each run of five ones. Now the flag 01111110 can never appear inside the data, so the receiver always knows where the frame ends.
The Ethernet frame
Ethernet (IEEE 802.3) frames have a fixed layout. On the wire they look like this:
| Field | Size | Purpose |
|---|---|---|
| Preamble | 7 bytes | Alternating 1s and 0s so the receiver can sync its clock |
| Start frame delimiter (SFD) | 1 byte | Marks the start of the frame |
| Destination MAC | 6 bytes | Who the frame is for |
| Source MAC | 6 bytes | Who sent it |
| EtherType | 2 bytes | Which protocol is inside: 0x0800 IPv4, 0x86DD IPv6, 0x0806 ARP |
| Payload | 46 to 1500 bytes | The packet from the layer above (padded if smaller than 46) |
| FCS | 4 bytes | CRC-32 error check over the frame |
The preamble and SFD are usually not counted as part of the frame. Without them, an Ethernet frame is 64 to 1518 bytes long. Note the destination comes first. A switch can start deciding where to send a frame as soon as the first 6 bytes arrive.
Here is a frame header unpacked with struct. The format "!6s6sH" means: 6 raw bytes, 6 raw bytes, then one 16-bit number.
import struct
frame = bytes.fromhex("ffffffffffff" "02005e1000aa" "0806") + b"...payload..."
dst, src, etype = struct.unpack("!6s6sH", frame[:14])
def mac(b):
return ":".join(f"{x:02x}" for x in b)
types = {0x0800: "IPv4", 0x86DD: "IPv6", 0x0806: "ARP"}
print("Destination:", mac(dst))
print("Source: ", mac(src))
print(f"EtherType: 0x{etype:04x} ({types.get(etype, 'unknown')})")Destination: ff:ff:ff:ff:ff:ff Source: 02:00:5e:10:00:aa EtherType: 0x0806 (ARP)
This is a broadcast ARP frame, sent to everyone on the LAN. You will see why in Switching & ARP.
MAC addresses
A MAC address (media access control address) is a 48-bit hardware address, written as six pairs of hex digits, such as 3c:22:fb:12:34:56. On Windows you see it with dashes: 3C-22-FB-12-34-56.
- The first three bytes are usually the OUI (organisationally unique identifier), a number the IEEE assigns to each manufacturer.
- The last three bytes are chosen by the manufacturer for each NIC.
Two bits in the first byte have special meaning:
- The lowest bit (the I/G bit): 0 means unicast (one device), 1 means multicast (a group).
- The next bit (the U/L bit): 0 means universally assigned by a maker, 1 means locally administered (set by software).
The all-ones address ff:ff:ff:ff:ff:ff is the broadcast address: every device on the LAN accepts it.
def describe(mac_text):
first = int(mac_text.replace("-", ":").split(":")[0], 16)
if mac_text.lower().replace("-", ":") == "ff:ff:ff:ff:ff:ff":
return f"{mac_text}: broadcast (every device on the LAN)"
if first & 1:
kind = "multicast"
else:
kind = "unicast"
origin = "locally administered" if first & 2 else "universal (maker)"
return f"{mac_text}: {kind}, {origin}"
for m in ["3c:22:fb:12:34:56", "ff:ff:ff:ff:ff:ff", "01:00:5e:00:00:fb", "da:a1:19:7c:20:01"]:
print(describe(m))3c:22:fb:12:34:56: unicast, universal (maker) ff:ff:ff:ff:ff:ff: broadcast (every device on the LAN) 01:00:5e:00:00:fb: multicast, universal (maker) da:a1:19:7c:20:01: unicast, locally administered
The last address is locally administered. Modern phones and laptops use random MAC addresses like this on Wi-Fi to protect your privacy, so shops and stations cannot track your device from network to network.
CSMA/CD and the minimum frame size
Early Ethernet put every computer on one shared cable. To share it, Ethernet used CSMA/CD (carrier sense multiple access with collision detection):
- Listen first. If the cable is busy, wait.
- If it is free, send.
- While sending, keep listening. If a collision is detected, stop, send a short jam signal, wait a random time, and try again.
For this to work, a sender must still be transmitting when news of a collision comes back. The worst case is a collision at the far end of the cable, whose news takes a full round trip 2 × Tp to return. So the frame must take at least that long to send:
- minimum frame size = 2 × Tp × R
Classic 10 Mbps Ethernet chose a slot time of 512 bits, which is where the 64-byte minimum frame comes from.
R = 10e6 # 10 Mbps
slot_bits = 512
print("Minimum frame:", slot_bits // 8, "bytes")
print(f"Slot time: {slot_bits / R * 1e6:.1f} microseconds")
# GATE style: given a link, find the minimum frame
d, v, R2 = 2000, 2e8, 100e6 # 2 km, 2x10^8 m/s, 100 Mbps
Tp = d / v
print("Min frame for 2 km at 100 Mbps:", round(2 * Tp * R2), "bits")Minimum frame: 64 bytes Slot time: 51.2 microseconds Min frame for 2 km at 100 Mbps: 2000 bits
Today's switched, full-duplex Ethernet has no shared cable and no collisions, so CSMA/CD is not used. The 64-byte minimum stayed for compatibility. Wi-Fi uses a cousin, CSMA/CA (collision avoidance), because radios cannot listen while they transmit.
Common mistakes
- Counting the preamble in the frame size. Exam answers of 64 and 1518 bytes leave it out.
- Thinking the MAC address is global. It is only used on the local link. Routers replace the MAC addresses at every hop.
- Reading the wrong bit. The multicast bit is the lowest bit of the first byte, not the first bit you see.
- Forgetting the 2 in 2 × Tp. The collision signal must travel back as well.
Key points
- The data link layer groups bits into frames, addresses them with MAC addresses and checks them for errors.
- Framing uses a length field, byte stuffing or bit stuffing (a 0 after five 1s).
- An Ethernet frame has destination, source, EtherType, 46 to 1500 bytes of payload and a 4-byte FCS: 64 to 1518 bytes.
- MAC addresses are 48 bits; the first byte's low bit marks multicast and the next bit marks local administration.
ff:ff:ff:ff:ff:ffis the broadcast address.- CSMA/CD needs frames at least 2 × Tp × R bits long, which gave classic Ethernet its 64-byte minimum.
Exercise
Write a function ethernet_size(payload) that returns the frame size in bytes (without preamble) for a given payload, padding payloads under 46 bytes and returning None for payloads over 1500. Test it with 10, 46, 1000, 1500 and 1600 bytes.
Show solution
def ethernet_size(payload):
if payload > 1500:
return None
return 14 + max(payload, 46) + 4
for p in [10, 46, 1000, 1500, 1600]:
print(p, "->", ethernet_size(p))10 -> 64 46 -> 64 1000 -> 1018 1500 -> 1518 1600 -> None
The 14-byte header and 4-byte FCS are always present. Small payloads are padded to 46 bytes, which gives the 64-byte minimum frame.
Quick Quiz
1. With bit stuffing, what is inserted after five consecutive 1s?
The sender inserts a 0 so the flag pattern 01111110 never appears inside data.
2. How long is a MAC address?
MAC addresses are 48 bits, written as six hex pairs.
3. EtherType 0x0806 means the payload is:
0x0800 is IPv4, 0x86DD is IPv6 and 0x0806 is ARP.
4. What is the minimum Ethernet frame size, without the preamble?
14 header + 46 minimum payload + 4 FCS = 64 bytes.
Learn Computer Networks with a mentor
Free lessons get you started. Our Computer Science Fundamentals training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
