Lab details

PPPoE Docker Interop Lab (Ze client vs accel-ppp)

Peer-isolated Docker lab proving Ze's PPPoE client interoperates with a real-world access concentrator: accel-ppp, the dominant open-source BRAS/AC.

Why this exists (and why not L2TP)

The original request was an L2TP interop test against accel-ppp. That is not buildable: an L2TP tunnel needs exactly one LAC (initiator) and one LNS (responder), and both Ze and accel-ppp are LNS-only. Ze defers LAC mode (internal/component/l2tp/session_fsm.go: "LAC-initiated incoming calls are deferred to a later spec"), and accel-ppp's L2TP module is a documented LNS with no supported client/LAC initiation. Two LNSes cannot form a tunnel.

PPPoE is the protocol where Ze and accel-ppp have complementary roles: accel-ppp's first-class role is the PPPoE server (access concentrator), and Ze has a full RFC 2516 PPPoE client (internal/component/l2tp/pppoeclient/, pppoe-client interface kind). This lab exercises exactly that pairing, which no other test covers: the existing test/pppoe/*.ci tests run Ze as the server against a synthetic client.

Overview

Ze runs as a PPPoE client and accel-ppp as the AC in two privileged Docker containers on an isolated user-defined bridge. Because PPPoE is an L2 protocol (EtherType 0x8863/0x8864), the shared bridge gives the broadcast PADI a path to the AC. The lab proves the complete client path: PADI/PADO/PADR/PADS discovery, LCP, CHAP-MD5 authentication, IPCP address assignment, kernel pppN interface creation with the server-assigned P2P address, dataplane ping to the AC gateway, the AC's own session view, and clean teardown when the client stops.

Layout

test/pppoe-interop/
  run.py               Runner: preflight, image build, scenario selection
  lab.py               Docker lifecycle, helpers, Ze/accel verification
  Dockerfile.ze        Ze PPPoE-client image (Alpine + ze + iproute2 + ppp + kmod)
  Dockerfile.accel     accel-ppp AC image (Alpine + apk add accel-ppp)
  entrypoint-accel.sh  modprobe ppp/pppoe, run accel-pppd in foreground
  scenarios/
    01-pppoe-chap-ipv4/  CHAP + IPv4 pool session proof

Each scenario contains ze.conf (Ze pppoe-client config), accel-ppp.conf (AC config), chap-secrets (credentials), and a check.py with a check() function imported by the runner.

Prerequisites

Docker and a host kernel with PPPoE support. The preflight probes for /dev/ppp and the pppoe (pppox PX_PROTO_OE) kernel module from inside a temporary privileged container. If either is missing, the runner exits non-zero with a clear message; it never skips or downgrades. Setting ZE_PPPOE_SKIP_KERNEL_PROBE or ze.pppoe.skip-kernel-probe causes an immediate refusal.

Docker Desktop on macOS typically cannot pass this check (its Linux VM lacks the pppoe module); use the QEMU path below there. The accel-ppp image installs the Alpine accel-ppp package (the same build the QEMU runner uses), so the image build is fast.

Running

Two paths exercise the same Ze-client-vs-accel-ppp pairing. Prefer the QEMU path on macOS or any host without PPPoE kernel support.

Docker (host kernel)

make ze-deployment-pppoe-accel-docker-test               # all scenarios
python3 test/pppoe-interop/run.py 01-pppoe-chap-ipv4     # single scenario
VERBOSE=1 python3 test/pppoe-interop/run.py              # debug output

Environment variables: VERBOSE, NO_BUILD, SESSION_TIMEOUT (default 90s), ZE_PPPOE_INTEROP_SUFFIX (default PID, for parallel-run isolation).

QEMU (macOS-friendly, no Docker)

make ze-kernel GOKRAZY_ARCH=arm64      # once: build runtime kernel with CONFIG_PPPOE
make ze-qemu-pppoe-accel-test

This boots the runtime kernel in a QEMU Alpine VM, installs accel-ppp from Alpine community, and runs scripts/evidence/effective-pppoe-accel.py -- the netns sibling of this lab (Ze client + accel-ppp server in two namespaces joined by a veth, no Docker). It is the canonical way to run this proof on a dev machine. See ai/rules/qemu-testing.md ("Interop Labs ... Need a QEMU Runner Too").

Scenarios

01-pppoe-chap-ipv4

Proves: PPPoE discovery completes, LCP opens, Ze authenticates to accel-ppp with CHAP-MD5, IPCP assigns the pool address (10.11.0.2) with the AC gateway (10.11.0.1) as peer, exactly one kernel pppN interface appears in Ze with that P2P address, accel-ppp's show sessions lists the subscriber, Ze pings the AC gateway through the session, and accel-ppp drops the session after the Ze client stops.

Relationship to Other Evidence

Target What it proves Ze role Kernel PPPoE required
test/pppoe/pppoe-basic.ci Server-side discovery (PADI/PADO/PADR/PADS) in a netns Server (AC) βœ•
test/pppoe/pppoe-vlan.ci Server-side discovery over VLAN Server (AC) βœ•
make ze-deployment-pppoe-accel-docker-test Full client path vs a real AC, Docker (this) Client βœ“
make ze-qemu-pppoe-accel-test Same proof in QEMU netns (effective-pppoe-accel.py) Client βœ“

The Docker lab and the QEMU runner prove the same thing with the same peer (accel-ppp from Alpine community); the QEMU runner is the one that works on a macOS dev machine. Per ai/rules/qemu-testing.md, a Linux-only interop lab must ship both.

The .ci tests exercise Ze as the access concentrator with a synthetic Python client; this lab is the only test where Ze is the client and the peer is a real, independent AC implementation.

Design Pattern

Follows the test/l2tp-interop/ lab pattern (see l2tp-interop.md): scenario directory with daemon configs, per-run Docker network with PID suffix, fixed container IPs, atexit global cleanup, strict kernel preflight, and check.py assertion scripts imported by the runner. It is a separate module because the roles are inverted (Ze is the client here) and the peer daemon, image, and helpers are PPPoE/accel-ppp specific.