LearnThe Network StackWhat Moves on the Wire

MAC Addresses

ip link show - a hands-on Linux lab on a real virtual machine.

MAC addresses identify network hardware. The MAC sublayer stamps them into every frame. This lesson teaches both, and clears up the confusion between them.

Two numbers that run this network

Somewhere inside this VM, two machines are wired together and trading packets. Here are their hardware addresses:

02:00:0a:00:01:0a
02:00:0a:00:01:14

By the end of this lesson you will read those the way you read a phone number. You will know which bit announces that software invented them, why the second one contains the number 20 in plain sight, and why neither address would survive one hop past a router.

You will also untangle the most quietly confused term in networking. MAC names two different things, and almost nobody separates them. After this lesson you will.

The black boxes below are a practice terminal: a safe sandbox that checks the one command each step teaches. The lab VM behind this lesson runs two tiny machines, client at 10.0.1.10 and server at 10.0.1.20, joined by a virtual cable, with helper commands send-ping, send-curl and cap from the Watching Traffic lesson already installed. Your progress in the real lab is tracked automatically, so type commands naturally.

What a MAC address is

A MAC address (Media Access Control address) is a 48-bit identifier assigned to a network interface. It is written as six pairs of hexadecimal digits separated by colons:

02:00:0a:00:01:0a

Each pair is called an octet because it holds 8 bits, one byte. Six octets make 48 bits, which allows about 281 trillion distinct addresses.

The textbook line says a MAC address is burned into the hardware like a serial number. Treat that as history rather than truth. On physical cards the factory does stamp one in. On everything virtual, and this VM is virtual, the address is minted by software at creation time. What never changes is the job: the MAC address is the name an interface answers to on its own local network segment.

You will meet the same address written with hyphens (02-00-0a-00-01-0a), with no separators (02000a00010a), or Cisco style with dots (0200.0a00.010a). Same 48 bits, different costumes. Linux uses colons.

Where 48 bits came from

The first Ethernet, built by Bob Metcalfe and David Boggs at Xerox PARC in 1973, used 8-bit addresses. 256 possible machines, because the whole network was one building. The 48-bit address arrived in 1980, when DEC, Intel and Xerox published the commercial Ethernet standard and sized the field not for one building but for every Ethernet device that would ever be built, anywhere, with no coordination between buyers.

That plan needed a registry. The IEEE runs it: a manufacturer buys a 24-bit prefix called an OUI (Organizationally Unique Identifier) and assigns the remaining 24 bits itself, one value per card. Cisco has held 00:00:0c since the 1980s. Sun workstations announced themselves with 08:00:20. Large vendors own hundreds of prefixes now, and Wireshark ships a copy of the registry, which is how it prints a vendor name next to every factory address it meets.

Read your machine's own address

Start with the interface your VM uses to reach the world: eth0, the same one you met in Lesson 1. The kernel publishes what it knows about every interface as plain files, in a virtual filesystem called sysfs, mounted at /sys. Interface details live under /sys/class/net/<name>/, and the file named address holds the MAC.

No flags, no parsing. Read the file.

Commit to the shape of the answer before you press Enter. A whole file is about to print. How many lines is it, and is there a label in front of the address?

prompt: student@mac-lab:~$ answer: cat /sys/class/net/eth0/address output: 02:fc:00:00:00:05 hint: cat the file named address under /sys/class/net/eth0/

One line, six octets, nothing else. That is the whole point of this file: when a script needs a MAC, this is the clean source. Your tail octets will differ, every VM gets its own, but the opening 02 repeats on every one, and in a few steps you will know exactly what it announces.

The address file has neighbors worth knowing:

FileWhat it holds
/sys/class/net/eth0/addressThe MAC address
/sys/class/net/eth0/operstateLink state: up, down, unknown
/sys/class/net/eth0/mtuLargest packet this interface carries in one frame
/sys/class/net/eth0/carrier1 if the link sees a signal, 0 if not

That path is long and you never have to type all of it. Type cat /sys/cl and press Tab, then continue a few letters at a time, pressing Tab after each piece. The shell completes each segment for you. Build the habit now; every long path in this track gets faster with it.

Commit: what will ip link show admit to?

The cold open promised two more machines inside this VM. They are built from network namespaces. A network namespace is a private copy of the kernel's networking world: its own interfaces, its own addresses, its own tables. This lab created two, named client and server, and moved one end of a virtual cable into each.

ip link show, the interface list from Lesson 1, is about to run in your shell, which lives in the default namespace, the one every process starts in. Commit before you look.

Prove it with ip link show

Run the list. While it prints, read the second line of each entry, the one that starts with link/.

prompt: student@mac-lab:~$ answer: ip link show ||| ip link ||| sudo ip link show output: 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000 link/ether 02:fc:00:00:00:05 brd ff:ff:ff:ff:ff:ff hint: The interface list command from Lesson 1: ip link show

Two interfaces, exactly as you committed. No trace of the lab machines.

Now the link/ lines. link/ether marks eth0 as an Ethernet device, and the address after it matches the file you read two steps ago. The brd ff:ff:ff:ff:ff:ff after that is the broadcast address: a frame sent to all ones is delivered to every interface on the local segment, which is how a machine shouts when it does not yet know who it is looking for.

lo, the loopback, shows 00:00:00:00:00:00. Loopback traffic never touches a wire, so it never needed a hardware name. An all-zero MAC is not an error here. It is the address of a place no frame ever leaves.

Reach into the client machine

sudo ip netns exec <name> <command> runs one command inside a namespace, as if you had logged into that machine. Use it to read the client's MAC the same way you read eth0's: straight from sysfs.

The client's interface is called veth-c. It is one end of a veth pair, a virtual patch cable made by the kernel: two interfaces, permanently wired, and whatever enters one comes out the other. veth-s, in the server namespace, is the other connector.

prompt: student@mac-lab:~$ answer: sudo ip netns exec client cat /sys/class/net/veth-c/address output: 02:00:0a:00:01:0a hint: sudo ip netns exec client, then the same cat you used on eth0, with veth-c as the interface name

The first address from the cold open. You just read a file that does not exist in your own /sys.

That works because of a detail most people never notice: ip netns exec does not just switch network namespaces. It also gives the command a private mount of /sys, rebuilt from the target namespace's point of view, so /sys/class/net/ lists that world's interfaces. Sysfs is not one global tree. It is a viewport, and the viewport follows the namespace.

Commit: the server's address is hiding something

The server's interface veth-s carries the second address from the cold open: 02:00:0a:00:01:14. Its IP address is 10.0.1.20.

Hexadecimal digits count 0 to 9 and then a to f, so one digit holds 16 values and a pair holds 256. Take a position on that last octet.

The first octet is a row of switches

How could this lab invent addresses without colliding with a real network card somewhere in the world? Because the first octet of a MAC is not just a number. Its two lowest bits are flags, and they carve up the entire address space.

Bit 0 is the individual/group bit. 0 means unicast: the address names one interface. 1 means multicast: the address names a group that interfaces can subscribe to. The all-ones broadcast address is simply the loudest multicast there is.

Bit 1 is the locally administered bit. 0 means universal: a factory address built on an IEEE OUI. 1 means local: somebody minted this address in software, and the IEEE guarantees no factory will ever ship it. 02 in binary is 00000010: bit 1 set, bit 0 clear. Locally administered unicast. That is the badge on both lab addresses, on your eth0, and on nearly every VM, container and namespace interface you will ever inspect.

You can classify any MAC on sight from its second hex digit alone:

Second hex digitWhat the address is
0, 4, 8, cFactory unicast: the first three octets are a real OUI
2, 6, a, eLocally administered unicast: made in software
any odd digitMulticast: a group, never a single machine

The middle row is why your phone shows a different Wi-Fi MAC on every network: randomization features mint locally administered addresses, which can never collide with a factory card. It is also why ethtool -P exists. On real hardware it prints the permanent factory address even after software has overridden the running one, which is how you catch a machine wearing a MAC that is not its own.

Ethernet transmits each octet least significant bit first, so the individual/group bit is the very first bit of the address on the wire. A switch knows whether a frame is for one port or for flooding before the rest of the destination has even arrived. That is not an accident. It is 1980s protocol design still paying rent.

Two things named MAC

Now the distinction this lesson exists for. MAC stands for Media Access Control, and it names two different things.

The MAC sublayer is a job description: the lower half of Layer 2, the machinery that takes a packet from the network layer and gets it onto the physical medium in one piece. Three duties:

1. Framing. Wrap the packet in a header and trailer so a receiver can tell where it starts and ends. 2. Media access. Decide when it is this machine's turn to transmit, so two senders do not corrupt each other on a shared wire or radio channel. 3. Error detection. Stamp a checksum on every frame so damaged ones are recognized and dropped.

The MAC address is a label: the identifier that machinery writes into every frame header, once for the sender, once for the intended receiver.

The address is named after the machinery, not the other way around. The post office is an institution with rules and procedures. Your house number is a label the institution requires. They share a name here because the label belongs to the machinery: no MAC sublayer, no MAC addresses on anything.

Textbooks split Layer 2 into LLC (Logical Link Control), the upper half, and MAC, the lower half. On modern Ethernet the LLC half has nearly withered away; about the only LLC frames a working engineer still captures are spanning tree BPDUs. The media access duty has thinned too. On a switched, full duplex link nobody shares a wire, and the standards dropped collision handling entirely from 10 Gigabit Ethernet onward. Wi-Fi is where that duty still earns its keep, arbitrating a radio channel every station shares. Wi-Fi frames also carry three, sometimes four, MAC address fields instead of two, because the radio hop and the final wired destination are different machines.

So when someone says the MAC on that box is wrong, they mean the address. When a datasheet says the MAC implements flow control, they mean the sublayer. From here on this track says MAC address or MAC sublayer, never a bare MAC, until you can hear which one is meant.

Where the addresses ride

You dissected a frame in Lesson 5. Look at the envelope again, this time reading it as the MAC sublayer's work order:

| Destination MAC | Source MAC | EtherType | Payload    | FCS |
|     6 bytes     |  6 bytes   |  2 bytes  | 46 to 1500 | 4 B |

Destination comes first, and that is deliberate. A device reading a frame off the wire holds the complete destination address after 6 bytes and can start choosing the exit port while the rest is still arriving. Cut-through switches do exactly that: they begin forwarding a frame they have not finished receiving.

The trailer is the FCS (Frame Check Sequence): a 4-byte CRC (Cyclic Redundancy Check), a checksum computed over the whole frame. The receiver recomputes it and compares.

Here is the part that surprises people: you will never see an FCS in a capture on these machines. The NIC verifies and strips it before the kernel gets the frame, and a frame that fails the check is dropped in hardware, invisible to tcpdump, surfacing only as an error tick in counters like rx_crc_errors from Lesson 3. When a capture tool reports a frame length, the FCS is not in it. Keep that in your pocket. It explains a number on the next screen.

Commit: who leads the reply?

Time to watch the addresses work. The client is about to ping the server. The request travels client to server. The reply comes back.

A capture prints one line per frame. For the reply frame, commit to where the client's address 02:00:0a:00:01:0a appears.

Watch it happen on live frames

cap is this lab's capture helper from Lesson 4: tshark listening on the client's interface. -T fields mode prints only the fields you name, and -e eth.src -e eth.dst names the two addresses in every Ethernet header. send-ping & starts pings in the background for the capture to catch.

Run the pair:

send-ping &
cap -c 3 -T fields -e eth.src -e eth.dst

prompt: student@mac-lab:~$ answer: cap -c 3 -T fields -e eth.src -e eth.dst ||| cap -c 2 -T fields -e eth.src -e eth.dst output: Running as user "root" and group "root". This could be dangerous. Capturing on 'veth-c' 02:00:0a:00:01:0a 02:00:0a:00:01:14 02:00:0a:00:01:14 02:00:0a:00:01:0a 02:00:0a:00:01:0a 02:00:0a:00:01:14 3 packets captured hint: The capture helper, a packet count, then -T fields with -e eth.src -e eth.dst

Three lines of data, wrapped in three lines of tshark talking about itself. The root warning and Capturing on 'veth-c' are the same pair you met in Lesson 4, and 3 packets captured is tshark reporting that it stopped where your count told it to. None of that came off the wire.

Now the data. Two columns: source, then destination. Line one is a request, client to server. Line two swaps both columns: the reply, exactly as you committed. Line three is the next request.

Decode them on sight now. 0a:00:01:0a is the tail of 10.0.1.10's address, 0a:00:01:14 the tail of 10.0.1.20's. These frames label themselves, because this lab minted them that way.

One quiet detail: on this two-machine cable, every frame involves the client, so nothing had to be let through specially. On a shared segment, a NIC normally drops frames addressed to other machines before the OS ever sees them. Capture tools flip the interface into promiscuous mode to turn that hardware filter off. It will matter when you build a bridge in Lesson 9.

The same frames, envelope printed

tcpdump, the second capture tool from Lesson 4, prints the Ethernet header when you add -e. Same traffic, different lens. Before you run it, try to predict the number that will follow the word length. The FCS fact is the key.

send-ping &
sudo ip netns exec client tcpdump -e -i veth-c -c 2

prompt: student@mac-lab:~$ answer: sudo ip netns exec client tcpdump -e -i veth-c -c 2 ||| sudo ip netns exec client tcpdump -i veth-c -e -c 2 output: tcpdump: verbose output suppressed, use -v[v]... for full protocol decode listening on veth-c, link-type EN10MB (Ethernet), snapshot length 262144 bytes 14:07:31.482913 02:00:0a:00:01:0a > 02:00:0a:00:01:14, ethertype IPv4 (0x0800), length 98: 10.0.1.10 > 10.0.1.20: ICMP echo request, id 1042, seq 1, length 64 14:07:31.482956 02:00:0a:00:01:14 > 02:00:0a:00:01:0a, ethertype IPv4 (0x0800), length 98: 10.0.1.20 > 10.0.1.10: ICMP echo reply, id 1042, seq 1, length 64 2 packets captured 2 packets received by filter 0 packets dropped by kernel hint: The tcpdump form from Lesson 4, with -e added to print the Ethernet header

Ignore the banner and the three counters for now; Lesson 4 covered them, and 0 packets dropped by kernel is the one worth a glance before you trust any capture.

Read one packet line left to right: timestamp, source MAC, an arrow, destination MAC, then ethertype IPv4 (0x0800), the frame length, and the Layer 3 story. Your timestamps and the id number will differ; both are per run.

Now settle length 98 byte by byte: 14 of Ethernet header, 20 of IP header, 8 of ICMP header, 56 of ping payload. 14

Practice MAC Addresses in a real Linux terminal at The Linux Camp. Progress is verified automatically as you type commands on the machine.

More lessons in What Moves on the Wire