Skip to content

Repository files navigation

PPPoE Proxy

A proxy system for PPPoE (Point-to-Point Protocol over Ethernet) connections written in Go.

Overview

PPPoE Proxy allows tunneling PPPoE connections between networks by proxying both discovery and session packets. This enables:

  • Extending PPPoE connectivity across networks
  • Controlling and monitoring PPPoE traffic
  • Adding authentication layers to PPPoE connections

The system operates in one of three modes:

  • Client Mode: Captures PPPoE traffic from a local interface and forwards it to a remote server
  • Server Mode: Receives traffic from clients and forwards it to the actual PPPoE server, with authentication
  • Tunnel Mode: Connects to a server and terminates PPPoE and PPP itself, exposing the link as a local tun interface. No local PPPoE traffic is forwarded and no pppd is needed.

Features

  • Proxying of PPPoE Discovery packets (0x8863)
  • Proxying of PPPoE Session packets (0x8864)
  • Raw socket handling for efficient packet capture and injection
  • IP-based access control for client connections
  • Automatic reconnection for client mode
  • Ping/pong keepalive mechanism (60-second interval)
  • Thread-safe connection handling
  • Built-in PPPoE, LCP, PAP/CHAP and IPCP implementation for tunnel mode

Usage

# Server Mode
./pppoeproxy -interface eth0 -mode server -address 0.0.0.0:8000 -allow 192.168.1.2

# Client Mode
./pppoeproxy -interface eth0 -mode client -address 192.168.1.1:8000

# Tunnel Mode
./pppoeproxy -mode tunnel -address 192.168.0.1:2433 -user "your-username" -password "your-password"

Command Line Options

  • -interface: Network interface to capture and inject PPPoE packets (required in client and server mode)
  • -mode: Operation mode: "client", "server" or "tunnel" (default: "client")
  • -address: Address to connect to (client and tunnel mode) or listen on (server mode) (required)
  • -allow: IP address allowed to connect (server mode only, default: "127.0.0.1")

Tunnel mode only:

  • -user: PPP authentication user name (required)
  • -password: PPP authentication password
  • -tun: Name of the local tun interface to create (default: "pppoe0")
  • -service: PPPoE service name to request (default: empty, which accepts any)
  • -mac: MAC address to use for the PPPoE session (default: a random locally administered address)

How It Works

  1. PPPoE Discovery Phase:

    • In client mode, captures PADI, PADO, PADR, and PADS packets
    • In server mode, captures and forwards packets to connected clients
    • Forwards packets between the client, server, and the actual PPPoE server
  2. PPPoE Session Phase:

    • Captures and forwards session packets to maintain the tunnel
    • Preserves PPPoE session IDs and packet integrity
  3. Tunnel Mode:

    • Connects to a server and runs the discovery exchange itself (PADI, PADO, PADR, PADS)
    • Negotiates LCP, authenticates with PAP or CHAP-MD5, then negotiates IPCP
    • Filters out every frame that is not part of its own session, so other PPPoE clients sharing the remote segment are ignored
    • Hands the resulting IP link to a local tun interface

Tunnel Mode

Client mode is a dumb relay: it needs a spare interface to inject PPPoE frames into and a local pppd to drive them. Tunnel mode does the whole job in one process instead. It performs the PPPoE discovery and the PPP session itself, so the only thing it needs locally is a tun interface.

sudo ./pppoeproxy -mode tunnel -address 192.168.0.1:2433 \
    -user "your-username" -password "your-password"

Once IPCP completes, the assigned address is logged along with the DNS servers the peer advertised:

PPPoE: session 0x0042 established with 02:ac:ac:ac:ac:01
PPP: CHAP authentication succeeded
Tunnel: pppoe0 up with address 203.0.113.7, peer 198.51.100.1, MTU 1492
Tunnel: DNS server 1: 8.8.8.8

Routing

Tunnel mode never touches the system routing table. Bringing the link up only assigns the address to the tun interface, which implicitly adds a host route to the peer and nothing else. There is no default route, so nothing about the machine's existing connectivity changes.

To actually send traffic over the link, route it explicitly:

# Send one destination over the tunnel
sudo ip route add 198.51.100.0/24 dev pppoe0

# Or use policy routing for everything sourced from the assigned address
sudo ip route add default dev pppoe0 table 100
sudo ip rule add from 203.0.113.7 lookup 100

The interface is created and brought up at startup, before the first session, so routes and rules pointing at it can be installed ahead of time. They survive reconnections: the interface is never taken down, only re-addressed.

MAC Addresses

You almost certainly need -mac. Tunnel mode defaults to a random locally administered MAC address, and the access concentrator unicasts its PADO back to that address. The server's capture interface only accepts frames addressed to its own MAC, so a PADO sent to a random address is dropped by the kernel before the raw socket ever sees it. The symptom is PADIs going out forever with no offer ever coming back:

PPPoE: still no offer after 5 attempts, slowing down retries

Pass the MAC address of the interface the server captures on:

sudo ./pppoeproxy -mode tunnel -address 192.168.0.1:2433 \
    -mac 00:11:22:33:44:55 -user "your-username" -password "your-password"

The alternative is to put the server's capture interface into promiscuous mode, which lets any MAC work:

sudo ip link set dev pppoe-proxy promisc on

Sharing the capture interface's MAC is fine even when that interface already has its own PPPoE session: sessions are demultiplexed by session ID, and everything that is not ours is dropped. Pinning the MAC also keeps the address stable across restarts, which some access concentrators prefer.

Session Lifetime

On a clean shutdown (SIGINT/SIGTERM) the client sends a PADT so the access concentrator releases the session immediately.

If the process is killed with SIGKILL it cannot do that, and the concentrator keeps the session allocated until it times out. On a line with a small session limit, such as the two sessions NTT allows, a leaked session means the next start gets no PADO at all until the old one expires.

Use Case: NTT Lines in Japan

In Japan, NTT allows up to 2 PPPoE sessions on a single line. This enables an interesting use case:

  1. Set up a macvlan interface on your primary internet-connected device
  2. Run this proxy in server mode on that macvlan interface
  3. Run a client on a remote device (e.g., in another location)
  4. The remote device can now establish its own PPPoE session through your NTT line

This effectively allows you to share your NTT connection with a remote location while maintaining separate PPPoE sessions, each with its own public IP address.

Setting Up the Macvlan Interface

On your primary device that's connected to the NTT line, create a macvlan interface:

# Create a macvlan interface attached to your physical interface (e.g., eth0)
sudo ip link add link eth0 name pppoe-proxy type macvlan mode bridge

# Bring the interface up
sudo ip link set dev pppoe-proxy up

Running the Server

On the same primary device, run the PPPoE proxy in server mode:

# Run the server on the macvlan interface, listening on port 8000
sudo ./pppoeproxy -interface pppoe-proxy -mode server -address 0.0.0.0:8000 -allow 192.168.1.2

Replace 192.168.1.2 with the IP address of your remote client device.

Ubiquiti Dream Machine Firewall Configuration

If you're running the server on a Ubiquiti Dream Machine (UDM/UDM-Pro), you'll need to open the port in the firewall to allow incoming connections from your client:

# SSH into your UDM and run:
iptables -I UBIOS_WAN_LOCAL_USER -p tcp --dport 8000 --src 192.168.1.2 -j ACCEPT

Replace 8000 with your server port and 192.168.1.2 with your client IP address.

Running the Client

On the remote device:

# Run the client connecting to the server's IP address
sudo ./pppoeproxy -interface eth0 -mode client -address 192.168.1.1:8000

Replace eth0 with your network interface and 192.168.1.1 with the IP address of your server.

Setting Up the PPPoE Client

On the remote device, configure your PPPoE client software to connect through the proxy:

# Example using pppd for Linux
sudo pppd plugin rp-pppoe.so eth0 user "your-username" password "your-password" noauth

Once connected, the remote device will have its own public IP address through the NTT line.

Using Tunnel Mode Instead

Tunnel mode replaces both the client process and pppd on the remote device, and needs no spare interface to inject frames into:

sudo ./pppoeproxy -mode tunnel -address 192.168.1.1:8000 \
    -user "your-username" -password "your-password"

The remote device gets its own public IP address on pppoe0, and its existing default route keeps working: see Tunnel Mode for how to send traffic over the link.

Installation

Download Pre-built Binary

You can download a pre-built version of pppoeproxy using the following command:

curl -s https://raw.githubusercontent.com/KarpelesLab/make-go/master/get.sh | /bin/sh -s pppoeproxy

This will automatically download the appropriate binary for your system architecture.

Building from Source

If you prefer to build from source:

git clone https://github.com/KarpelesLab/pppoeproxy.git
cd pppoeproxy
make

Requirements

  • Go 1.20 or higher
  • Linux system with root access (for raw socket operations)
  • Administrative privileges on network interfaces

About

Simple client/server proxy for PPPoE

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages