Networking for beginners. Part 4: PPPoE
All parts of the series “Networking for beginners”In the first part the link layer was simply "deliver to the neighbour"; in the second and third we saw how Ethernet and Wi-Fi do it. Today is about a protocol that sits between Ethernet and IP and turns up in almost every home router: PPPoE, Point-to-Point Protocol over Ethernet. In the router settings it is that very "Connection type: PPPoE" option next to which you enter the username and password from your ISP.
To understand PPPoE you need to understand two things: what PPP is and why it had to be wrapped in Ethernet.
The ISP's problem
The ISP has a switch in the building with cables running from it to the flats. From Ethernet's point of view, all the subscribers in the building are just ports on one switch. But the ISP needs to:
- know who has connected: whether the service is paid for, which plan;
- account: session time, traffic volume;
- hand out an address and other parameters, and take them back when the subscriber disconnects;
- manage the session: cut off for non-payment, limit the speed.
Ethernet has none of this: a frame either arrived or it did not, and who sent it is known only from the MAC address, which anyone can spoof. But all of it existed in PPP — a protocol written for the dial-up connections of the 1990s, where a subscriber dialled into the ISP's modem pool and entered a username and password before going online. When modems gave way to Ethernet and DSL, ISPs did not invent anything new; they took PPP and taught it to run over Ethernet. That is how RFC 2516 appeared in 1999.
Today there is another way too — IPoE: the address is assigned via DHCP (covered in part 11), and the subscriber is identified by the switch port or MAC address. But PPPoE is still widespread, especially where access goes through a DSL modem: ADSL and VDSL carry data over a telephone line, the modem on the subscriber's side turns it back into Ethernet, and from there PPPoE takes over.
PPP: a protocol for a point-to-point line
PPP (RFC 1661) is a link-layer protocol for connecting exactly two parties. It has no MAC addresses: with only two on the line, there is nobody to address. A bare PPP frame is simple:
bytes: 2 variable
┌──────────┬────────────────────┐
│ Protocol │ data │
└──────────┴────────────────────┘
The Protocol field is our "what's inside" field, like EtherType in Ethernet:
| Protocol | What is inside |
|---|---|
0xc021 |
LCP — control of the link itself |
0xc023 |
PAP — password authentication |
0xc223 |
CHAP — challenge-response authentication |
0x8021 |
IPCP — configuring IPv4 over the link |
0x8057 |
IPv6CP — configuring IPv6 |
0x0021 |
IPv4 packet |
0x0057 |
IPv6 packet |
The pattern: protocols with codes 0x8xxx configure the 0x0xxx family, and 0xcxxx ones are control protocols for the link itself.
The main thing in PPP is the phases of a connection's life. The link does not simply "come up": the parties agree on parameters step by step, check each other, and only then configure IP.
physical ┌───────┐ ┌──────────────────┐ ┌─────────────┐ ┌────────┐
link is up ──►│ LCP ├──►│ authentication ├──►│ IPCP/IPv6CP ├──►│ data │
└───┬───┘ │ (PAP or CHAP) │ └──────┬──────┘ └───┬────┘
│ └──────────────────┘ │ │
└──────── Terminate ◄─────────────────┴──────────────┘
LCP: agreeing on the link
LCP (Link Control Protocol) opens the connection. Both sides send a Configure-Request with a list of the options they want to use. Each request can get one of three responses:
- Configure-Ack — agree with all the options;
- Configure-Nak — I understand the option but don't like the value, here is mine (for example, "MRU not 1500 but 1492");
- Configure-Reject — I don't understand or don't want the option, remove it.
The sender adjusts the request and sends it again until both sides have received an Ack. This is the common mechanism of all the "CP" protocols in PPP: IPCP works exactly the same way, just with other options.
LCP options that appear in every PPPoE session:
- MRU (Maximum Receive Unit) — the size of packets a side is willing to receive. In PPPoE it is 1492, and why is explained below.
- Authentication-Protocol — the server demands authentication and says which kind: PAP or CHAP. The client usually does not send this option, since it has no need to check the ISP.
- Magic-Number — a random number from each side. If a side sees its own number in the reply to its Echo-Request, the line is looped back and it is talking to itself.
Once open, LCP keeps working as a watchdog for the link: it periodically sends Echo-Request, waits for Echo-Reply, and if several replies in a row do not arrive, it considers the link dead and closes the session. That is how a router "sees" that the ISP has vanished and starts reconnecting. Closing is Terminate-Request and Terminate-Ack.
Authentication: PAP and CHAP
PAP (Password Authentication Protocol) could not be simpler: the client sends an Authenticate-Request with the username and password in plain text, and the server replies with Authenticate-Ack or Authenticate-Nak. Anyone who sees the frames sees the password. In PPPoE inside the ISP's network this is considered tolerable, but CHAP is better.
CHAP (Challenge Handshake Authentication Protocol) does not send the password over the line:
- The server sends a Challenge: a random string and its name.
- The client computes
MD5(identifier, password, challenge)and sends a Response: the hash and its username. - The server, knowing the password, computes the same thing, compares and replies Success or Failure.
The password is not visible, and an intercepted response is useless: next time the challenge will be different. The weakness lies elsewhere: the server has to store passwords in plain text to compute the same hash. There is a variant, MS-CHAPv2, in which both sides verify each other and the hash is computed differently, but the scheme is the same: challenge, response, verdict.
The ISP checks the username and password not on the concentrator itself but on a RADIUS server: a protocol over UDP with which network equipment asks a central database "should this user be let in", gets Accept or Reject, and along with Accept — the parameters: the subscriber's address, the speed limit, the session lifetime. The concentrator later sends it accounting records about the start and end of the session and the traffic volume. That is the answer to "why does an ISP need PPP": all the bookkeeping hangs on one protocol.
IPCP: getting an address
Authentication has passed, and now IP is negotiated with the same Request/Ack/Nak scheme. IPCP (RFC 1332) has few options, and the main one is IP-Address. The trick is in how the client gets its address: it sends a Configure-Request with the IP-Address option set to 0.0.0.0, which means "I have no address, give me one". The server replies with a Configure-Nak with the same option but a real value: "not 0.0.0.0, but 198.51.100.42". The client repeats the request with that address and gets an Ack. The DNS server addresses are passed the same way, via options 0x81 and 0x83.
The server, for its part, also sends a Configure-Request with its own address, and the client acknowledges it. In the end the link has two ends with two addresses, and in ip addr it looks like this:
5: ppp0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1492 qdisc fq_codel state UNKNOWN
link/ppp
inet 198.51.100.42 peer 198.51.100.1/32 scope global ppp0
Note NOARP and peer: the interface has no MAC address and no subnet mask in the usual sense — there is only "me" and "whoever is at the other end". The default route is simply default dev ppp0: everything that is not mine goes into the link, and no gateway address is needed for that.
For IPv6 the same job is done by IPv6CP, except it has just one option — the interface identifier (the lower 64 bits of the address) — and the subscriber then gets the prefix itself through the IPv6 protocols that parts 14–16 will cover.
After IPCP the link is in the "open" state, and from then on ordinary IP packets travel in frames with Protocol 0x0021.
PPPoE: PPP inside an Ethernet frame
Now let's wrap this in Ethernet. PPP has no addresses, while the ISP's segment has hundreds of subscribers and one or two access concentrators (Access Concentrator, also known as BRAS — Broadband Remote Access Server). You first need to find a concentrator and set up a separate session with it, and then send PPP frames specifically to it. So PPPoE has two stages with different EtherTypes:
- Discovery, EtherType
0x8863— finding the concentrator and establishing a session; - Session, EtherType
0x8864— carrying PPP frames within the session.
The PPPoE header is 6 bytes, the same in both stages:
bytes: 1 1 2 2
┌────┬────┬────────┬────────────┬──────────┐
│Ver │Type│ Code │ Session ID │ Length │ then tags (discovery)
│ =1 │ =1 │ │ │ │ or a PPP frame (session)
└────┴────┴────────┴────────────┴──────────┘
(Ver and Type are 4 bits each)
Code is the message type in the discovery stage or 0x00 for data, Session ID is the session number the concentrator will assign, Length is the length of the contents.
Discovery: four frames
client concentrator(s)
│ │
├── PADI ──────────────────────────── broadcast ───────►│ "who here speaks PPPoE?"
│ ff:ff:ff:ff:ff:ff, Service-Name, Host-Uniq │
│ │
│◄─ PADO ─────────────────────────────────── from each ─┤ "me, bras-01, I can"
│ AC-Name, Service-Name, AC-Cookie │
│ │
├── PADR ─────────────── to the chosen one, unicast ───►│ "I want a session with you"
│ Service-Name, AC-Cookie │
│ │
│◄─ PADS ───────────────────────────────────────────────┤ "session 0x1a2b is open"
│ Session ID │
│ │
│ ═══════ Session stage: PPP frames with Session ID ═══ │
│ │
├── PADT ──────────────────────────── either side ─────►│ "session closed"
- PADI (PPPoE Active Discovery Initiation), code
0x09. The client knows nothing yet and sends a frame to the broadcast address. Inside are the Service-Name tag (usually empty — "any service") and Host-Uniq — a random number by which the client will later recognise replies to its own request among others. - PADO (Offer), code
0x07. Every concentrator that hears the PADI and is ready to serve replies with a unicast frame carrying the tags AC-Name (its name), Service-Name and AC-Cookie — a value the client must return unchanged. The cookie protects the concentrator from flooding: it does not create any state until the client proves it really received the reply. - PADR (Request), code
0x19. The client picks one of the PADOs (usually the first) and sends it a session request with the same AC-Cookie. - PADS (Session-confirmation), code
0x65. The concentrator issues a Session ID, a non-zero 16-bit number. From this moment the session is identified by a triple: the client MAC, the concentrator MAC, the Session ID.
PADT (Terminate), code 0xa7, closes the session from either side. A curious detail: PADT comes after PPP has already been closed via LCP Terminate, but if the concentrator has rebooted and forgotten the session, it will send a PADT in response to the very first frame with an unknown Session ID, and the client will learn it needs to reconnect.
If nobody answers a PADI, the client repeats it at increasing intervals. This is that very "the router says Connecting and nothing happens" situation: the cable is there, the link is up, but there is no concentrator behind it — usually because the port in the building is in the wrong VLAN.
Session: PPP as is
In the Session stage, every frame carries, after the 6-byte PPPoE header with code 0x00 and the right Session ID, a PPP frame: 2 bytes of Protocol and the data. So a subscriber's IP packet on the cable looks like this:
┌──────────────┬────────────┬──────────┬────────────┬─────┐
│ Ethernet 14 │ PPPoE 6 │ PPP 2 │ IP packet │ FCS │
│ EtherType │ code 0x00 │ 0x0021 │ │ │
│ 0x8864 │ session ID │ │ │ │
└──────────────┴────────────┴──────────┴────────────┴─────┘
This is the usual nesting doll from the first part, with two layers inserted between Ethernet and IP: PPPoE and PPP. The concentrator strips all three headers, looks at IP and routes it onwards.
MTU 1492
The picture above leads to the arithmetic that everyone remembers PPPoE for. An Ethernet frame holds 1500 bytes of data. Of these, the PPPoE header eats 6 and PPP 2. That leaves 1492 for the IP packet. This is exactly the value the sides tell each other in LCP as the MRU, and exactly the MTU the ppp0 interface gets.
Why this is a problem. The rest of the internet lives with an MTU of 1500, and the server at the other end, knowing nothing about PPPoE, sends the subscriber 1500-byte packets. The concentrator cannot squeeze such a packet into the session. Then, by the rules of IP (part 6), it must either cut the packet into pieces or, if the sender forbade that with the DF flag, drop it and send the sender an ICMP message "too big, reduce to 1492" (detailed in part 8 — this is the Path MTU Discovery mechanism). Modern stacks always set the DF flag, so the second path is what remains, and it works as long as the ICMP message reaches the server. If somewhere along the way a firewall drops ICMP "just in case", the server will keep sending 1500-byte packets into the void. Everyone knows the symptom: pages with small responses open, large files and images hang, ping works.
There are two fixes on the subscriber's side:
- MSS clamping on the router. When a TCP connection is established, the sides tell each other the maximum segment size, MSS (part 10). The router rewrites this value in passing packets to 1452 (1492 minus 40 bytes of IP and TCP headers), and the server never sends more from the very start. On Linux it is one rule:
iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu. Almost all home routers do this on their own. - RFC 4638: if the ISP's concentrator and switches pass Ethernet frames 8 bytes larger than the standard (1508), a PPP-Max-Payload tag is added to PADI/PADO, and the session MTU becomes an honest 1500. Not every ISP supports it.
Seeing it for yourself
On Linux, NetworkManager can bring up PPPoE (nmcli connection add type pppoe ifname eth1 username user password secret), and under the hood everywhere there is the pppd daemon with the rp-pppoe plugin. Its log tells the whole story of the connection:
pppd[1234]: Plugin rp-pppoe.so loaded.
pppd[1234]: PPP session is 6699
pppd[1234]: Connected to 00:11:22:33:44:55 via interface eth1
pppd[1234]: Using interface ppp0
pppd[1234]: Connect: ppp0 <--> eth1
pppd[1234]: CHAP authentication succeeded
pppd[1234]: peer from calling number 00:11:22:33:44:55 authorized
pppd[1234]: local IP address 198.51.100.42
pppd[1234]: remote IP address 198.51.100.1
pppd[1234]: primary DNS address 198.51.100.53
The discovery stage is visible in tcpdump with the pppoed filter, the session with pppoes:
$ sudo tcpdump -i eth1 -n -e pppoed
3c:52:82:1a:2b:3c > ff:ff:ff:ff:ff:ff, ethertype PPPoE D (0x8863), length 32: PPPoE PADI [Service-Name] [Host-Uniq 0x00000001]
00:11:22:33:44:55 > 3c:52:82:1a:2b:3c, ethertype PPPoE D (0x8863), length 60: PPPoE PADO [AC-Name "bras-01"] [Service-Name] [AC-Cookie 0x...] [Host-Uniq 0x00000001]
3c:52:82:1a:2b:3c > 00:11:22:33:44:55, ethertype PPPoE D (0x8863), length 48: PPPoE PADR [Service-Name] [AC-Cookie 0x...] [Host-Uniq 0x00000001]
00:11:22:33:44:55 > 3c:52:82:1a:2b:3c, ethertype PPPoE D (0x8863), length 60: PPPoE PADS [ses 0x1a2b] [Service-Name] [Host-Uniq 0x00000001]
$ sudo tcpdump -i eth1 -n pppoes
PPPoE [ses 0x1a2b] LCP, Conf-Request (0x01), id 1, length 16
PPPoE [ses 0x1a2b] LCP, Conf-Ack (0x02), id 1, length 16
PPPoE [ses 0x1a2b] CHAP, Challenge (0x01), id 1, Value ..., Name bras-01
PPPoE [ses 0x1a2b] CHAP, Response (0x02), id 1, Value ..., Name user
PPPoE [ses 0x1a2b] CHAP, Success (0x03), id 1
PPPoE [ses 0x1a2b] IPCP, Conf-Request (0x01), id 1, length 10
PPPoE [ses 0x1a2b] IPCP, Conf-Nak (0x03), id 1, length 10
PPPoE [ses 0x1a2b] IPCP, Conf-Request (0x01), id 2, length 10
PPPoE [ses 0x1a2b] IPCP, Conf-Ack (0x02), id 2, length 10
PPPoE [ses 0x1a2b] IP 198.51.100.42 > 203.0.113.10: ICMP echo request, ...
Everything this article covered is here in chronological order: the four discovery frames with their tags, LCP, CHAP with its challenge and response, IPCP with that very Nak in which the concentrator reports the address, and finally IP. In Wireshark each layer expands separately: Ethernet, PPPoE with the Session ID, PPP with the Protocol field, and then either LCP/CHAP/IPCP with their options, or IP.
Key takeaways
- PPP is a link-layer protocol for two parties without addresses, with phases: LCP negotiates the link, PAP or CHAP checks the subscriber, IPCP assigns the address. All the "CP"s work with one scheme: Request, then Ack, Nak or Reject.
- PPPoE exists to set up a PPP session over shared Ethernet: discovery (PADI → PADO → PADR → PADS) finds the concentrator and obtains a Session ID, after which PPP frames travel inside Ethernet with that number.
- For the ISP this provides authentication, accounting and session management via RADIUS; for the subscriber, a username and password in the router settings.
- The 8 bytes of PPPoE and PPP headers leave 1492 for the IP packet. Without MSS clamping or working Path MTU Discovery, large responses from the internet do not get through.
- LCP Echo is the link's watchdog; PADT is its end.
This concludes the block on the link layer: we can deliver frames to a neighbour over cable, over radio and through an ISP. Next — Part 5: IPv4 — addresses, masks and subnets: what an IP address and a mask are, how subnets are calculated from them, how a machine decides whether the destination is a neighbour or a gateway is needed, and which addresses you will never see on the internet.
Only registered users can leave a comment.
Sign in or register to leave a comment.