Configuring a PPPoE Server on Linux Using rp-pppoe for ISP Labs

Network tutorial - IT technology blog
Network tutorial - IT technology blog

Quick Start (Lab Setup in 5 Minutes)

Need a lightweight PPPoE server on Ubuntu or Debian to quickly test a MikroTik, Cisco, or OpenWrt router? Here are the ready-to-run commands to get you up and running.

Install the required packages, including pppoe (which contains the rp-pppoe suite), the ppp daemon, and iptables:

sudo apt update
sudo apt install -y ppp pppoe iptables

Create a sample session configuration file at /etc/ppp/pppoe-server-options:

sudo bash -c 'cat <<EOF > /etc/ppp/pppoe-server-options
require-chap
login
lcp-echo-interval 10
lcp-echo-failure 2
ms-dns 8.8.8.8
ms-dns 1.1.1.1
netmask 255.255.255.255
default-mru 1492
default-asyncmap
EOF'

Define dial-in accounts in /etc/ppp/chap-secrets. You can assign a static IP or let the server allocate one automatically:

# client        server  secret          IP addresses
"client01"      *       "Password@123"  10.0.0.50
"client02"      *       "Password@456"  *

Enable IP forwarding and configure NAT. Assuming eth0 connects to the Internet and eth1 connects to the lab switch:

sudo sysctl -w net.ipv4.ip_forward=1
sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
sudo iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu

Start the PPPoE server daemon on the eth1 interface:

sudo pppoe-server -I eth1 -L 10.0.0.1 -R 10.0.0.10 -N 20

Done. Connect a cable from your client router to the eth1 port, dial in with user client01, and you will see the client receive IP 10.0.0.50 and access the Internet normally.

Technical Deep Dive

Why DHCP Cannot Replace PPPoE in ISP Labs

DHCP is sufficient for home or office networks. However, when building a lab to simulate an ISP (BNG/BRAS), everything changes, and DHCP’s shortcomings quickly become apparent.

DHCP does not require user authentication. It struggles to isolate sessions per subscriber contract and makes it nearly impossible to apply dynamic per-session QoS policies. Consequently, telecom operators like VNPT, Viettel, and FPT still rely heavily on PPPoE for residential FTTH infrastructure.

How Does the PPPoE Handshake Work?

PPPoE encapsulates traditional PPP frames directly into Ethernet frames. The handshake process consists of two distinct stages:

  • Discovery Stage (Layer 2): The client and server locate each other through four sequential packets: PADI (client broadcasts to discover servers) → PADO (server unicasts an offer with available services) → PADR (client requests a session) → PADS (server confirms and assigns a Session ID).
  • PPP Session Stage: Once the Session ID is established, both sides negotiate LCP (Link Control Protocol). Next comes password authentication via CHAP. Finally, IPCP negotiates network parameters to agree on the IP address, default gateway, and DNS servers.

Deconstructing pppoe-server Command-Line Flags

A concise single-line command that packs all essential runtime parameters:

  • -I eth1: The interface listening for Discovery packets (PADI). This network port does not need a pre-assigned IP address. PPPoE Discovery frames run directly at Layer 2 with EtherType 0x8863.
  • -L 10.0.0.1: The server-side tunnel endpoint IP. The client will use this address as its Default Gateway.
  • -R 10.0.0.10: The starting IP for automatic allocation (IP Pool). If you specify * in chap-secrets, the server will allocate incrementally starting from this IP.
  • -N 20: Caps concurrent sessions at a maximum of 20. This prevents the VM from exhausting memory if clients flood broadcast requests.

Optimization & Operations

1. Resolving the MTU Trap and TCP MSS Clamping

A standard Ethernet frame has an MTU of 1500 bytes. The PPPoE header consumes 6 bytes, plus 2 bytes for the PPP Protocol ID, resulting in 8 bytes of overhead. Consequently, the maximum MTU on the virtual pppX interface drops to exactly 1492 bytes.

This is where problems arise.

Clients typically negotiate TCP connections with a default MSS of 1460 bytes. Combined with a 20-byte IP header and a 20-byte TCP header, packet size reaches 1500 bytes. Once encapsulated inside the PPPoE tunnel, the total frame swells to 1508 bytes and is immediately fragmented.

Worse, when packets have the DF (Don’t Fragment) bit set, intermediate routers drop them while inadvertently filtering out ICMP Type 3 Code 4 (Fragmentation Needed) error messages. This creates a classic “black hole” symptom: ping replies come back at 1ms, but loading HTTPS websites or downloading files hangs indefinitely.

The cleanest fix is enforcing TCP MSS clamping in the iptables mangle table:

sudo iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu

This rule inspects the interface MTU and clamps the MSS in SYN packets down to a safe value (typically 1452 bytes), restoring smooth web traffic.

2. Wrapping into a Systemd Service

To have the server start automatically whenever the lab VM reboots, create a service file at /etc/systemd/system/pppoe-server.service:

[Unit]
Description=RP-PPPoE Server Service
After=network.target

[Service]
Type=forking
ExecStartPre=/sbin/iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
ExecStartPre=/sbin/iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
ExecStart=/usr/sbin/pppoe-server -I eth1 -L 10.0.0.1 -R 10.0.0.10 -N 50
ExecStop=/usr/bin/killall pppoe-server
Restart=on-failure

[Install]
WantedBy=multi-user.target

Reload the daemon and start the service:

sudo systemctl daemon-reload
sudo systemctl enable --now pppoe-server

Field Notes & Troubleshooting

While stress-testing a lab router setup for a 60-person office, I once ran into a bizarre packet-drop issue. Throughout the morning, general web browsing and Google Meet calls worked flawlessly. By afternoon, however, users began complaining that they couldn’t upload 15MB PDF files or download images from Nextcloud.

Running tcpdump on the ppp0 interface finally uncovered the culprit: an intermediate core switch was silently dropping all ICMP Type 3 Code 4 messages. Large image transfers maxed out the packet size to 1500 bytes with the DF=1 bit set. Packets were silently dropped, leaving clients unaware that they needed to lower their MTU.

Ever since that incident, whenever I set up a PPPoE lab or GRE tunnel, the TCPMSS --clamp-mss-to-pmtu rule is the first thing I add to the mangle table before testing any other service.

Real-Time Session Monitoring and Debugging

Whenever a client successfully connects, the Linux kernel creates a virtual interface such as ppp0 or ppp1. You can quickly inspect active connections with:

ip link show | grep ppp

To troubleshoot incorrect passwords or failed LCP negotiations, follow logs in real time using journalctl:

sudo journalctl -t pppd -f

If the root cause remains unclear, add a debug directive to /etc/ppp/pppoe-server-options. Every LCP ConfReq, ConfAck frame, and CHAP Challenge packet will be logged in detail, pinpointing the issue within minutes.

Share: