Chapter 30. PPP

30.1. Synopsis

FreeBSD supports the Point-to-Point (PPP) protocol which can be used to establish a network or Internet connection using a dial-up modem. This chapter describes how to configure modem-based communication services in FreeBSD.

Read this chapter to learn:

  • How to configure, use, and troubleshoot a PPP connection.

  • How to set up PPP over Ethernet (PPPoE).

  • How to set up PPP over ATM (PPPoA).

Before reading this chapter:

  • Be familiar with basic network terminology.

  • Understand the basics and purpose of a dial-up connection and PPP.

30.2. Configuring PPP

FreeBSD provides built-in support for managing dial-up PPP connections using ppp(8). The default FreeBSD kernel provides support for tun which is used to interact with a modem hardware. Configuration is performed by editing at least one configuration file, and configuration files containing examples are provided. Finally, ppp is used to start and manage connections.

In order to use a PPP connection, the following items are needed:

  • A dial-up account with an Internet Service Provider (ISP).

  • A dial-up modem.

  • The dial-up number for the ISP.

  • The login name and password assigned by the ISP.

  • The IP address of one or more DNS servers. Normally, the ISP provides these addresses. If it did not, FreeBSD can be configured to use DNS negotiation.

If any of the required information is missing, contact the ISP.

The following information may be supplied by the ISP, but is not necessary:

  • The IP address of the default gateway. If this information is unknown, the ISP will automatically provide the correct value during connection setup. When configuring PPP on FreeBSD, this address is referred to as HISADDR.

  • The subnet mask. If the ISP has not provided one, 255.255.255.255 will be used in the ppp(8) configuration file. *

    If the ISP has assigned a static IP address and hostname, it should be input into the configuration file. Otherwise, this information will be automatically provided during connection setup.

The rest of this section demonstrates how to configure FreeBSD for common PPP connection scenarios. The required configuration file is /etc/ppp/ppp.conf and additional files and examples are available in /usr/share/examples/ppp/.

Throughout this section, many of the file examples display line numbers. These line numbers have been added to make it easier to follow the discussion and are not meant to be placed in the actual file.

When editing a configuration file, proper indentation is important. Lines that end in a : start in the first column (beginning of the line) while all other lines should be indented as shown using spaces or tabs.

30.2.1. Basic Configuration

In order to configure a PPP connection, first edit /etc/ppp/ppp.conf with the dial-in information for the ISP. This file is described as follows:

1     default:
2       set log Phase Chat LCP IPCP CCP tun command
3       ident user-ppp VERSION
4       set device /dev/cuau0
5       set speed 115200
6       set dial "ABORT BUSY ABORT NO\\sCARRIER TIMEOUT 5 \
7                 \"\" AT OK-AT-OK ATE1Q0 OK \\dATDT\\T TIMEOUT 40 CONNECT"
8       set timeout 180
9       enable dns
10
11    provider:
12      set phone "(123) 456 7890"
13      set authname foo
14      set authkey bar
15      set timeout 300
16      set ifaddr x.x.x.x/0 y.y.y.y/0 255.255.255.255 0.0.0.0
17      add default HISADDR
Line 1

Identifies the default entry. Commands in this entry (lines 2 through 9) are executed automatically when ppp is run.

Line 2

Enables verbose logging parameters for testing the connection. Once the configuration is working satisfactorily, this line should be reduced to:

set log phase tun
Line 3

Displays the version of ppp(8) to the PPP software running on the other side of the connection.

Line 4

Identifies the device to which the modem is connected, where COM1 is /dev/cuau0 and COM2 is /dev/cuau1.

Line 5

Sets the connection speed. If 115200 does not work on an older modem, try 38400 instead.

Lines 6 & 7

The dial string written as an expect-send syntax. Refer to chat(8) for more information.

Note that this command continues onto the next line for readability. Any command in ppp.conf may do this if the last character on the line is \.

Line 8

Sets the idle timeout for the link in seconds.

Line 9

Instructs the peer to confirm the DNS settings. If the local network is running its own DNS server, this line should be commented out, by adding a # at the beginning of the line, or removed.

Line 10

A blank line for readability. Blank lines are ignored by ppp(8).

Line 11

Identifies an entry called provider. This could be changed to the name of the ISP so that load ISP can be used to start the connection.

Line 12

Use the phone number for the ISP. Multiple phone numbers may be specified using the colon (:) or pipe character (|) as a separator. To rotate through the numbers, use a colon. To always attempt to dial the first number first and only use the other numbers if the first number fails, use the pipe character. Always enclose the entire set of phone numbers between quotation marks (") to prevent dialing failures.

Lines 13 & 14

Use the user name and password for the ISP.

Line 15

Sets the default idle timeout in seconds for the connection. In this example, the connection will be closed automatically after 300 seconds of inactivity. To prevent a timeout, set this value to zero.

Line 16

Sets the interface addresses. The values used depend upon whether a static IP address has been obtained from the ISP or if it instead negotiates a dynamic IP address during connection.

If the ISP has allocated a static IP address and default gateway, replace x.x.x.x with the static IP address and replace y.y.y.y with the IP address of the default gateway. If the ISP has only provided a static IP address without a gateway address, replace y.y.y.y with 10.0.0.2/0.

If the IP address changes whenever a connection is made, change this line to the following value. This tells ppp(8) to use the IP Configuration Protocol (IPCP) to negotiate a dynamic IP address:

set ifaddr 10.0.0.1/0 10.0.0.2/0 255.255.255.255 0.0.0.0
Line 17

Keep this line as-is as it adds a default route to the gateway. The HISADDR will automatically be replaced with the gateway address specified on line 16. It is important that this line appears after line 16.

Depending upon whether ppp(8) is started manually or automatically, a /etc/ppp/ppp.linkup may also need to be created which contains the following lines. This file is required when running ppp in -auto mode. This file is used after the connection has been established. At this point, the IP address will have been assigned and it is now possible to add the routing table entries. When creating this file, make sure that provider matches the value demonstrated in line 11 of ppp.conf.

provider:
      add default HISADDR

This file is also needed when the default gateway address is "guessed" in a static IP address configuration. In this case, remove line 17 from ppp.conf and create /etc/ppp/ppp.linkup with the above two lines. More examples for this file can be found in /usr/share/examples/ppp/.

By default, ppp must be run as root. To change this default, add the account of the user who should run ppp to the network group in /etc/group.

Then, give the user access to one or more entries in /etc/ppp/ppp.conf with allow. For example, to give fred and mary permission to only the provider: entry, add this line to the provider: section:

allow users fred mary

To give the specified users access to all entries, put that line in the default section instead.

30.2.2. Advanced Configuration

It is possible to configure PPP to supply DNS and NetBIOS nameserver addresses on demand.

To enable these extensions with PPP version 1.x, the following lines might be added to the relevant section of /etc/ppp/ppp.conf.

enable msext
set ns 203.14.100.1 203.14.100.2
set nbns 203.14.100.5

And for PPP version 2 and above:

accept dns
set dns 203.14.100.1 203.14.100.2
set nbns 203.14.100.5

This will tell the clients the primary and secondary name server addresses, and a NetBIOS nameserver host.

In version 2 and above, if the set dns line is omitted, PPP will use the values found in /etc/resolv.conf.

30.2.2.1. PAP and CHAP Authentication

Some ISPs set their system up so that the authentication part of the connection is done using either of the PAP or CHAP authentication mechanisms. If this is the case, the ISP will not give a login: prompt at connection, but will start talking PPP immediately.

PAP is less secure than CHAP, but security is not normally an issue here as passwords, although being sent as plain text with PAP, are being transmitted down a serial line only. There is not much room for crackers to "eavesdrop".

The following alterations must be made:

13      set authname MyUserName
14      set authkey MyPassword
15      set login
Line 13

This line specifies the PAP/CHAP user name.Insert the correct value for MyUserName.

Line 14

This line specifies the PAP/CHAP password. Insert the correct value for MyPassword. An additional line may be added, such as:

16      accept PAP

or

16      accept CHAP

to make it obvious that this is the intention, but PAP and CHAP are both accepted by default.

Line 15

The ISP will not normally require a login to the server when using PAP or CHAP. Therefore, disable the "set login" string.

30.2.2.2. Using PPP Network Address Translation Capability

PPP has ability to use internal NAT without kernel diverting capabilities. This functionality may be enabled by the following line in /etc/ppp/ppp.conf:

nat enable yes

Alternatively, NAT may be enabled by command-line option -nat. There is also /etc/rc.conf knob named ppp_nat, which is enabled by default.

When using this feature, it may be useful to include the following /etc/ppp/ppp.conf options to enable incoming connections forwarding:

nat port tcp 10.0.0.2:ftp ftp
nat port tcp 10.0.0.2:http http

or do not trust the outside at all

nat deny_incoming yes

30.2.3. Final System Configuration

While ppp is now configured, some edits still need to be made to /etc/rc.conf.

Working from the top down in this file, make sure the hostname= line is set:

hostname="foo.example.com"

If the ISP has supplied a static IP address and name, use this name as the host name.

Look for the network_interfaces variable. To configure the system to dial the ISP on demand, make sure the tun0 device is added to the list, otherwise remove it.

network_interfaces="lo0 tun0"
ifconfig_tun0=

The ifconfig_tun0 variable should be empty, and a file called /etc/start_if.tun0 should be created. This file should contain the line:

ppp -auto mysystem

This script is executed at network configuration time, starting the ppp daemon in automatic mode. If this machine acts as a gateway, consider including -alias. Refer to the manual page for further details.

Make sure that the router program is set to NO with the following line in /etc/rc.conf:

router_enable="NO"

It is important that the routed daemon is not started, as routed tends to delete the default routing table entries created by ppp.

It is probably a good idea to ensure that the sendmail_flags line does not include the -q option, otherwise sendmail will attempt to do a network lookup every now and then, possibly causing the machine to dial out. Try this:

sendmail_flags="-bd"

The downside is that sendmail is forced to re-examine the mail queue whenever the ppp link. To automate this, include !bg in ppp.linkup:

1     provider:
2       delete ALL
3       add 0 0 HISADDR
4       !bg sendmail -bd -q30m

An alternative is to set up a "dfilter" to block SMTP traffic. Refer to the sample files for further details.

30.2.4. Using ppp

All that is left is to reboot the machine. After rebooting, either type:

# ppp

and then dial provider to start the PPP session, or, to configure ppp to establish sessions automatically when there is outbound traffic and start_if.tun0 does not exist, type:

# ppp -auto provider

It is possible to talk to the ppp program while it is running in the background, but only if a suitable diagnostic port has been set up. To do this, add the following line to the configuration:

set server /var/run/ppp-tun%d DiagnosticPassword 0177

This will tell PPP to listen to the specified UNIX® domain socket, asking clients for the specified password before allowing access. The %d in the name is replaced with the tun device number that is in use.

Once a socket has been set up, the pppctl(8) program may be used in scripts that wish to manipulate the running program.

30.2.5. Configuring Dial-in Services

“Dial-in Service” provides a good description on enabling dial-up services using getty(8).

An alternative to getty is comms/mgetty+sendfax port), a smarter version of getty designed with dial-up lines in mind.

The advantages of using mgetty is that it actively talks to modems, meaning if port is turned off in /etc/ttys then the modem will not answer the phone.

Later versions of mgetty (from 0.99beta onwards) also support the automatic detection of PPP streams, allowing clients scriptless access to the server.

Refer to http://mgetty.greenie.net/doc/mgetty_toc.html for more information on mgetty.

By default the comms/mgetty+sendfax port comes with the AUTO_PPP option enabled allowing mgetty to detect the LCP phase of PPP connections and automatically spawn off a ppp shell. However, since the default login/password sequence does not occur it is necessary to authenticate users using either PAP or CHAP.

This section assumes the user has successfully compiled, and installed the comms/mgetty+sendfax port on his system.

Ensure that /usr/local/etc/mgetty+sendfax/login.config has the following:

/AutoPPP/ -     - /etc/ppp/ppp-pap-dialup

This tells mgetty to run ppp-pap-dialup for detected PPP connections.

Create an executable file called /etc/ppp/ppp-pap-dialup containing the following:

#!/bin/sh
exec /usr/sbin/ppp -direct pap$IDENT

For each dial-up line enabled in /etc/ttys, create a corresponding entry in /etc/ppp/ppp.conf. This will happily co-exist with the definitions we created above.

pap:
  enable pap
  set ifaddr 203.14.100.1 203.14.100.20-203.14.100.40
  enable proxy

Each user logging in with this method will need to have a username/password in /etc/ppp/ppp.secret, or alternatively add the following option to authenticate users via PAP from /etc/passwd.

enable passwdauth

To assign some users a static IP number, specify the number as the third argument in /etc/ppp/ppp.secret. See /usr/share/examples/ppp/ppp.secret.sample for examples.

30.3. Troubleshooting PPP Connections

This section covers a few issues which may arise when using PPP over a modem connection. Some ISPs present the ssword prompt while others present password. If the ppp script is not written accordingly, the login attempt will fail. The most common way to debug ppp connections is by connecting manually as described in this section.

30.3.1. Check the Device Nodes

When using a custom kernel, make sure to include the following line in the kernel configuration file:

device   uart

The uart device is already included in the GENERIC kernel, so no additional steps are necessary in this case. Just check the dmesg output for the modem device with:

# dmesg | grep uart

This should display some pertinent output about the uart devices. These are the COM ports we need. If the modem acts like a standard serial port, it should be listed on uart1, or COM2. If so, a kernel rebuild is not required. When matching up, if the modem is on uart1, the modem device would be /dev/cuau1.

30.3.2. Connecting Manually

Connecting to the Internet by manually controlling ppp is quick, easy, and a great way to debug a connection or just get information on how the ISP treats ppp client connections. Lets start PPP from the command line. Note that in all of our examples we will use example as the hostname of the machine running PPP. To start ppp:

# ppp
ppp ON example> set device /dev/cuau1

This second command sets the modem device to cuau1.

ppp ON example> set speed 115200

This sets the connection speed to 115,200 kbps.

ppp ON example> enable dns

This tells ppp to configure the resolver and add the nameserver lines to /etc/resolv.conf. If ppp cannot determine the hostname, it can manually be set later.

ppp ON example> term

This switches to "terminal" mode in order to manually control the modem.

deflink: Entering terminal mode on /dev/cuau1
type '~h' for help
at
OK
atdt123456789

Use at to initialize the modem, then use atdt and the number for the ISP to begin the dial in process.

CONNECT

Confirmation of the connection, if we are going to have any connection problems, unrelated to hardware, here is where we will attempt to resolve them.

ISP Login:myusername

At this prompt, return the prompt with the username that was provided by the ISP.

ISP Pass:mypassword

At this prompt, reply with the password that was provided by the ISP. Just like logging into FreeBSD, the password will not echo.

Shell or PPP:ppp

Depending on the ISP, this prompt might not appear. If it does, it is asking whether to use a shell on the provider or to start ppp. In this example, ppp was selected in order to establish an Internet connection.

Ppp ON example>

Notice that in this example the first p has been capitalized. This shows that we have successfully connected to the ISP.

Ppp ON example>

We have successfully authenticated with our ISP and are waiting for the assigned IP address.

PPP ON example>

We have made an agreement on an IP address and successfully completed our connection.

PPP ON example>add default HISADDR

Here we add our default route, we need to do this before we can talk to the outside world as currently the only established connection is with the peer. If this fails due to existing routes, put a bang character ! in front of the add. Alternatively, set this before making the actual connection and it will negotiate a new route accordingly.

If everything went good we should now have an active connection to the Internet, which could be thrown into the background using CTRL+z. If PPP returns to ppp the connection has been lost. This is good to know because it shows the connection status. Capital P’s represent a connection to the ISP and lowercase p’s show that the connection has been lost.

30.3.3. Debugging

If a connection cannot be established, turn hardware flow CTS/RTS to off using set ctsrts off. This is mainly the case when connected to some PPP-capable terminal servers, where PPP hangs when it tries to write data to the communication link, and waits for a Clear To Send (CTS) signal which may never come. When using this option, include set accmap as it may be required to defeat hardware dependent on passing certain characters from end to end, most of the time XON/XOFF. Refer to ppp(8) for more information on this option and how it is used.

An older modem may need set parity even. Parity is set at none be default, but is used for error checking with a large increase in traffic, on older modems.

PPP may not return to the command mode, which is usually a negotiation error where the ISP is waiting for negotiating to begin. At this point, using ~p will force ppp to start sending the configuration information.

If a login prompt never appears, PAP or CHAP authentication is most likely required. To use PAP or CHAP, add the following options to PPP before going into terminal mode:

ppp ON example> set authname myusername

Where myusername should be replaced with the username that was assigned by the ISP.

ppp ON example> set authkey mypassword

Where mypassword should be replaced with the password that was assigned by the ISP.

If a connection is established, but cannot seem to find any domain name, try to ping(8) an IP address. If there is 100 percent (100%) packet loss, it is likely that a default route was not assigned. Double check that add default HISADDR was set during the connection. If a connection can be made to a remote IP address, it is possible that a resolver address has not been added to /etc/resolv.conf. This file should look like:

domain example.com
nameserver x.x.x.x
nameserver y.y.y.y

Where x.x.x.x and y.y.y.y should be replaced with the IP address of the ISP’s DNS servers.

To configure syslog(3) to provide logging for the PPP connection, make sure this line exists in /etc/syslog.conf:

!ppp
*.*     /var/log/ppp.log

30.4. Using PPP over Ethernet (PPPoE)

This section describes how to set up PPP over Ethernet (PPPoE).

Here is an example of a working ppp.conf:

default:
  set log Phase tun command # add more detailed logging when needed
  set ifaddr 10.0.0.1/0 10.0.0.2/0

name_of_service_provider:
  set device PPPoE:xl1 # replace xl1 with the Ethernet device
  set authname THELOGINNAME
  set authkey THEPASSWORD
  set dial
  set login
  add default HISADDR

As root, run:

# ppp -ddial name_of_service_provider

Add the following to /etc/rc.conf:

ppp_enable="YES"
ppp_mode="ddial"
ppp_nat="YES"	# when needing to enable nat for the local network, otherwise NO
ppp_profile="name_of_service_provider"

30.4.1. Using a PPPoE Service Tag

Sometimes it will be necessary to use a service tag to establish the connection. Service tags are used to distinguish between different PPPoE servers attached to a given network.

Any required service tag information should be in the documentation provided by the ISP.

As a last resort, one could try installing the net/rr-pppoe package or port. Bear in mind however, this may de-program the modem and render it useless, so think twice before doing it. Simply install the program shipped with the modem. Then, access the System menu from the program. The name of the profile should be listed there. It is usually ISP.

The profile name (service tag) will be used in the PPPoE configuration entry in ppp.conf as the provider part for set device. Refer to ppp(8) for full details. It should look like this:

set device PPPoE:xl1:ISP

Do not forget to change xl1 to the proper device for the Ethernet card.

Do not forget to change ISP to the profile.

For additional information, refer to Cheaper Broadband with FreeBSD on DSL by Renaud Waldura.

30.4.2. PPPoE with a 3Com® HomeConnect™ ADSL Modem Dual Link

This modem does not follow the PPPoE specification defined in RFC 2516.

In order to make FreeBSD capable of communicating with this device, a sysctl must be set. This can be done automatically at boot time by updating /etc/sysctl.conf:

net.graph.nonstandard_pppoe=1

or can be done immediately with the command:

# sysctl net.graph.nonstandard_pppoe=1

Unfortunately, because this is a system-wide setting, it is not possible to talk to a normal PPPoE client or server and a 3Com® HomeConnect™ ADSL Modem at the same time.

30.5. Using PPP over ATM (PPPoA)

The following describes how to set up PPP over ATM (PPPoA). PPPoA is a popular choice among European DSL providers.

30.5.1. Using mpd

The mpd application can be used to connect to a variety of services, in particular PPTP services. It can be installed using the net/mpd5 package or port. Many ADSL modems require that a PPTP tunnel is created between the modem and computer.

Once installed, configure mpd to suit the provider’s settings. The port places a set of sample configuration files which are well documented in /usr/local/etc/mpd/. A complete guide to configure mpd is available in HTML format in /usr/ports/shared/doc/mpd/. Here is a sample configuration for connecting to an ADSL service with mpd. The configuration is spread over two files, first the mpd.conf:

This example mpd.conf only works with mpd 4.x.

default:
    load adsl

adsl:
    new -i ng0 adsl adsl
    set bundle authname username (1)
    set bundle password password (2)
    set bundle disable multilink

    set link no pap acfcomp protocomp
    set link disable chap
    set link accept chap
    set link keep-alive 30 10

    set ipcp no vjcomp
    set ipcp ranges 0.0.0.0/0 0.0.0.0/0

    set iface route default
    set iface disable on-demand
    set iface enable proxy-arp
    set iface idle 0

    open
1The username used to authenticate with the ISP.
2The password used to authenticate with the ISP.

Information about the link, or links, to establish is found in mpd.links. An example mpd.links to accompany the above example is given beneath:

adsl:
    set link type pptp
    set pptp mode active
    set pptp enable originate outcall
    set pptp self 10.0.0.1 (1)
    set pptp peer 10.0.0.138 (2)
1The IP address of FreeBSD computer running mpd.
2The IP address of the ADSL modem. The Alcatel SpeedTouch™ Home defaults to 10.0.0.138.

It is possible to initialize the connection easily by issuing the following command as root:

# mpd -b adsl

To view the status of the connection:

% ifconfig ng0
ng0: flags=88d1<UP,POINTOPOINT,RUNNING,NOARP,SIMPLEX,MULTICAST> mtu 1500
     inet 216.136.204.117 --> 204.152.186.171 netmask 0xffffffff

Using mpd is the recommended way to connect to an ADSL service with FreeBSD.

30.5.2. Using pptpclient

It is also possible to use FreeBSD to connect to other PPPoA services using net/pptpclient.

To use net/pptpclient to connect to a DSL service, install the port or package, then edit /etc/ppp/ppp.conf. An example section of ppp.conf is given below. For further information on ppp.conf options consult ppp(8).

adsl:
 set log phase chat lcp ipcp ccp tun command
 set timeout 0
 enable dns
 set authname username (1)
 set authkey password (2)
 set ifaddr 0 0
 add default HISADDR
1The username for the DSL provider.
2The password for the account.

Since the account’s password is added to ppp.conf in plain text form, make sure nobody can read the contents of this file:

# chown root:wheel /etc/ppp/ppp.conf
# chmod 600 /etc/ppp/ppp.conf

This will open a tunnel for a PPP session to the DSL router. Ethernet DSL modems have a preconfigured LAN IP address to connect to. In the case of the Alcatel SpeedTouch™ Home, this address is 10.0.0.138. The router’s documentation should list the address the device uses. To open the tunnel and start a PPP session:

# pptp address adsl

If an ampersand ("&") is added to the end of this command, pptp will return the prompt.

A tun virtual tunnel device will be created for interaction between the pptp and ppp processes. Once the prompt is returned, or the pptp process has confirmed a connection, examine the tunnel:

% ifconfig tun0
tun0: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> mtu 1500
        inet 216.136.204.21 --> 204.152.186.171 netmask 0xffffff00
	Opened by PID 918

If the connection fails, check the configuration of the router, which is usually accessible using a web browser. Also, examine the output of pptp and the contents of the log file, /var/log/ppp.log for clues.

30.6. Multi-Worker PPPoE Load Balancer

FreeBSD includes a multi-worker PPPoE load balancer that distributes PPPoE sessions across multiple workers for improved performance and scalability. This feature uses the ng_pppoe_lb netgraph node type and pppoed daemon with built-in auto-scaling capabilities.

This section describes how to configure and use the multi-worker PPPoE load balancer.

30.6.1. Overview

The multi-worker PPPoE load balancer consists of:

  • Kernel module (ng_pppoe_lb.ko): Provides the netgraph node type for session distribution

  • pppoed daemon: Manages worker processes and implements the auto-scaling governor

  • ngctl commands: Administrative interface for monitoring and control

  • Automatic CPU governor: Dynamically scales workers based on system load

Key features:

  • Distributes PPPoE sessions across multiple worker processes

  • Session affinity: sessions stay on the same worker for their lifetime

  • Built-in auto-scaling governor that adjusts worker count based on CPU usage

  • Session-aware worker management: draining workers stop receiving new sessions

  • No external dependencies (part of FreeBSD base system)

30.6.2. Kernel Module Configuration

Load the required kernel modules:

# kldload netgraph
# kldload ng_ether
# kldload ng_pppoe
# kldload ng_pppoe_lb

To load these modules automatically at boot, add to /boot/loader.conf:

netgraph_load="YES"
ng_ether_load="YES"
ng_pppoe_load="YES"
ng_pppoe_lb_load="YES"

30.6.3. Basic Configuration

30.6.3.1. Creating the Load Balancer Node

Create a PPPoE load balancer node using ngctl(8):

# ngctl -f- <<EOF
mkpeer pppoe lb lower pppoe_lb
name pppoe_lb:pppoed
msg pppoe_lb:pppoed setconfig ${ethernet_iface}
EOF

Where ${ethernet_iface} is the Ethernet interface connected to the DSLAM (e.g., em0).

30.6.3.2. Starting pppoed with Multiple Workers

Start the pppoed daemon with multiple workers:

# pppoed -L -w 4 em0

This starts pppoed with 4 workers listening on interface em0.

Command line options:

  • -L: Enable multi-worker mode

  • -w count: Number of worker processes (default: 1)

  • -a address: PPPoE access concentrator name (optional)

  • -p profile: Service name (optional)

30.6.3.3. rc.conf Configuration

Add to /etc/rc.conf:

pppoed_enable="YES"
pppoed_flags="-L -w 4 em0"
pppoed_ng_pppae_lb_enable="YES"

30.6.4. Auto-Scaling Governor

The built-in governor automatically adjusts the number of workers based on CPU usage.

30.6.4.1. Enabling the Governor

Enable the governor at runtime:

# sysctl net.graph.pppoe_lb.governor.enabled=1

Or configure in /etc/sysctl.conf:

net.graph.pppoe_lb.governor.enabled=1

30.6.4.2. Governor Configuration

The governor can be configured via sysctl:

# Governor settings
net.graph.pppoe_lb.governor.enabled=1           # Enable governor
net.graph.pppoe_lb.governor.mode=1              # 0=manual, 1=auto
net.graph.pppoe_lb.governor.min_workers=2       # Minimum workers
net.graph.pppoe_lb.governor.max_workers=0       # 0=auto (use mp_ncpus)

# Thresholds
net.graph.pppoe_lb.governor.cpu_threshold=80     # Scale up at 80% CPU
net.graph.pppoe_lb.governor.cpu_low_threshold=30 # Scale down at 30% CPU

# Timing
net.graph.pppoe_lb.governor.scale_up_interval=10   # Seconds between scale-up
net.graph.pppoe_lb.governor.scale_down_interval=60 # Seconds between scale-down
net.graph.pppoe_lb.governor.drain_timeout=30      # Seconds to drain worker

30.6.4.3. Governor Behavior

The governor monitors CPU usage and automatically scales workers:

  • Scale up: When CPU usage exceeds cpu_threshold (default 80%), a new worker is created

  • Scale down: When CPU usage falls below cpu_low_threshold (default 30%), a worker is marked for removal

  • Session awareness: Workers marked for removal stop receiving new sessions but keep existing sessions

  • Change of heart: If load increases before a worker is removed, the removal is canceled

30.6.4.4. Worker States

Workers can be in one of three states:

  • ACTIVE: Accepting new sessions, processing packets normally

  • DRAINING: No new sessions assigned, existing sessions complete naturally

  • PENDING_REMOVAL: Empty, ready for removal

View worker states:

# ngctl msg pppoe_lb:pppoed showworkers

Or via sysctl:

# sysctl net.graph.pppoe_lb.workers

30.6.5. Monitoring

30.6.5.1. Using ngctl

View load balancer status:

# ngctl msg pppoe_lb:pppoed showworkers
Worker Status:
  Worker 0: ACTIVE (23 sessions, 45m uptime)
  Worker 1: ACTIVE (31 sessions, 45m uptime)
  Worker 2: DRAINING (5 sessions, 10m uptime)
  Worker 3: ACTIVE (28 sessions, 45m uptime)

View governor status:

# ngctl msg pppoe_lb:pppoed showgovernor
Governor Status:
  Enabled: Yes
  Mode: Auto
  Min Workers: 2
  Max Workers: 4
  Current Workers: 3
  Active Workers: 2
  Draining Workers: 1
  Pending Removals: 0
  Last Decision: scale_down (reason: cpu_low)

30.6.5.2. Using sysctl

View all PPPoE load balancer sysctls:

# sysctl net.graph.pppoe_lb

Key sysctls:

  • net.graph.pppoe_lb.governor.enabled: Enable/disable governor

  • net.graph.pppoe_lb.governor.current_workers: Current number of workers

  • net.graph.pppoe_lb.governor.active_workers: Workers in ACTIVE state

  • net.graph.pppoe_lb.governor.total_sessions: Total active sessions

  • net.graph.pppoe_lb.governor.cpu_usage: Current CPU usage percentage

  • net.graph.pppoe_lb.governor.last_decision: Last scaling decision

30.6.5.3. Per-Worker Statistics

View per-worker statistics:

# sysctl net.graph.pppoe_lb.workers.0
net.graph.pppoe_lb.workers.0.state: 0
net.graph.pppoe_lb.workers.0.sessions: 23
net.graph.pppoe_lb.workers.0.bytes_in: 1234567890
net.graph.pppoe_lb.workers.0.bytes_out: 987654321
net.graph.pppoe_lb.workers.0.uptime: 2700

30.6.6. Manual Worker Management

30.6.6.1. Adding a Worker

Add a worker manually:

# ngctl msg pppoe_lb:pppoed addworker

Or via sysctl:

# sysctl net.graph.pppoe_lb.governor.desired_workers=5

30.6.6.2. Removing a Worker

Mark a worker for removal (draining):

# ngctl msg pppoe_lb:pppoed rmworker 2

Or set worker state directly:

# sysctl net.graph.pppoe_lb.workers.2.state=1

The worker will stop receiving new sessions and be removed when empty.

30.6.6.3. Canceling Worker Removal

If a worker is draining but load increases, cancel the removal:

# sysctl net.graph.pppoe_lb.workers.2.state=0

The worker returns to ACTIVE state and can receive new sessions.

30.6.7. Session Affinity

The load balancer maintains session affinity: once a session is assigned to a worker, it stays on that worker.

30.6.7.1. How Affinity Works

When a new PPPoE session arrives:

  1. The load balancer receives the PADI packet

  2. It selects a worker using the configured algorithm

  3. The session is created on that worker

  4. All subsequent packets for that session go to the same worker

30.6.7.2. Affinity Algorithms

Configure the session distribution algorithm:

# Round-robin (default)
# ngctl msg pppoe_lb:pppoed setalgorithm 0

# Hash-based (by source MAC)
# ngctl msg pppoe_lb:pppoed setalgorithm 1

# Least-loaded (fewest sessions)
# ngctl msg pppoe_lb:pppoed setalgorithm 2

30.6.7.3. Viewing Session Mappings

View which worker a session is on:

# ngctl msg pppoe_lb:pppoed getmap
Session Mappings:
  Session 0x7f3a2b: Worker 2
  Session 0x8c4d1e: Worker 1
  Session 0x9a1f7c: Worker 3

30.6.8. Troubleshooting

30.6.8.1. Governor Not Scaling Up

If the governor doesn’t scale up when expected:

  1. Verify governor is enabled:

    # sysctl net.graph.pppoe_lb.governor.enabled

    Should return 1.

  2. Check current CPU usage:

    # sysctl net.graph.pppoe_lb.governor.cpu_usage

    CPU must exceed cpu_threshold to trigger scale-up.

  3. Check worker count isn’t at maximum:

    # sysctl net.graph.pppoe_lb.governor.max_workers

    If set to 0, maximum is mp_ncpus. If already at maximum, governor cannot add more.

30.6.8.2. Governor Not Scaling Down

If the governor doesn’t scale down:

  1. Verify workers are draining:

    # sysctl net.graph.pppoe_lb.governor.draining_workers

    Must be greater than 0 for scale-down to complete.

  2. Check scale-down interval hasn’t elapsed:

    # sysctl net.graph.pppoe_lb.governor.scale_down_interval

    Default is 60 seconds. Governor waits this long between scale-down decisions.

  3. Verify workers have few sessions:

    # sysctl net.graph.pppoe_lb.governor.avg_sessions_worker

    CPU must be below cpu_low_threshold for scale-down.

30.6.8.3. Sessions Not Distributing Evenly

If sessions aren’t balanced across workers:

  1. Check for affinity issues:

    Sessions from the same MAC always go to the same worker (hash algorithm).

  2. Verify worker states:

    Workers in DRAINING state don’t receive new sessions:

    # sysctl net.graph.pppoe_lb.governor.draining_workers
  3. Check for stuck sessions:

    Some sessions may not disconnect, preventing worker removal:

    # ngctl msg pppoe_lb:pppoed showworkers

30.6.8.4. Kernel Module Not Loading

If ng_pppoe_lb fails to load:

  1. Verify the module exists:

    # ls /boot/kernel/ng_pppoe_lb.ko
  2. Check for dependency errors:

    # kldload ng_pppoe_lb

    Look for missing dependency messages.

30.6.8.5. pppoed Won’t Start

If pppoed fails to start:

  1. Check for port conflicts:

    # sockstat | grep pppoed
  2. Verify ng_pppoe_lb node exists:

    # ngctl list | grep pppoe
  3. Check logs:

    # tail -f /var/log/messages | grep pppoed

30.6.9. Performance Tuning

30.6.9.1. Number of Workers

The optimal number of workers depends on:

  • CPU cores: Each worker can use one CPU core

  • Expected sessions: 500 sessions per worker is a reasonable target

  • Traffic volume: CPU-intensive tasks benefit from more workers

Starting values:

  • For testing: 2 workers

  • For production: Match mp_ncpus or slightly less

  • For high-load: mp_ncpus * 1.5 (if governor is enabled)

30.6.9.2. CPU Thresholds

Tune governor thresholds based on workload:

  • CPU threshold (scale up): Lower values (e.g., 60%) scale up earlier

  • CPU low threshold (scale down): Higher values (e.g., 40%) scale down later

More aggressive scaling (lower thresholds) provides better performance but may create/destroy workers more frequently.

30.6.9.3. Monitoring Performance

Monitor system performance:

# sysctl net.graph.pppoe_lb.governor.cpu_usage net.graph.pppoe_lb.governor.total_sessions
net.graph.pppoe_lb.governor.cpu_usage: 67
net.graph.pppoe_lb.governor.total_sessions: 847

If CPU usage is consistently high, consider:

  • Increasing the number of workers

  • Adding more CPU cores

  • Offloading some sessions to another server

30.6.10. Example Configurations

This section provides complete, production-ready configurations for common use cases. Each example includes the necessary configuration files, an explanation of what the settings do, and verification steps.

30.6.10.1. Basic Two-Worker Setup

Purpose: For dual-core systems, development environments, or initial testing. The governor is disabled to provide predictable, static behavior.

Architecture:

                            +------------------+
                            |   pppoe_lb       |
    DSLAM <-- Ethernet -->  |   (dispatcher)   |<---> Worker 0
    em0                     |                  |<---> Worker 1
                            +------------------+

When to Use:

  • Development and testing environments

  • Systems with exactly 2 CPU cores

  • When you want predictable, static worker counts

  • Initial deployment before moving to auto-scaling

When Not to Use:

  • Production with >500 concurrent sessions

  • Variable or unpredictable load patterns

  • High-availability requirements

Configuration Files:

# /etc/rc.conf
pppoed_enable="YES"
pppoed_flags="-L -w 2 em0"

# /etc/sysctl.conf (optional)
# Disable governor for predictable behavior
net.graph.pppoe_lb.governor.enabled=0

What Each Setting Does:

  • -L: Enable multi-worker mode

  • -w 2: Start with exactly 2 workers

  • governor.enabled=0: Disable auto-scaling (static configuration)

Verification Steps:

# 1. Check that pppoed is running with 2 workers
$ sockstat | grep pppoed
root     pppoed     1234  3  tcp4   *:2000                *:*
root     pppoed     1235  3  tcp4   *:2000                *:*
root     pppoed     1236  3  tcp4   *:2000                *:*

# 2. Verify worker count
$ sysctl net.graph.pppoe_lb.governor.current_workers
net.graph.pppoe_lb.governor.current_workers: 2

# 3. Check session distribution
$ ngctl msg pppoe_lb:pppoed showworkers
Worker Status:
  Worker 0: ACTIVE (0 sessions, 1h uptime)
  Worker 1: ACTIVE (0 sessions, 1h uptime)

Expected Behavior:

  • Sessions are distributed round-robin between workers 0 and 1

  • New sessions always go to the worker with fewer existing sessions

  • Workers never scale up or down automatically

  • If a worker crashes, it is not automatically replaced

Troubleshooting:

# Problem: Only 1 worker showing
$ sysctl net.graph.pppoe_lb.governor.current_workers
net.graph.pppoe_lb.governor.current_workers: 1

# Solution: Check if pppoed restarted; may need manual restart
$ service pppoed restart

30.6.10.2. Auto-Scaling Production Setup

Purpose: For production environments with variable load. The governor automatically adjusts worker count based on CPU utilization.

Architecture:

    +-----------+
    |  Governor  |    <-- Monitors CPU every 5 seconds
    |  (scaler)   |
    +------+------+    scale up/down signals
           |
           v
    +------------------+     +------------------+
    |   pppoe_lb       |---->| Worker Pool      |
    |   (dispatcher)   |     | (grows: 2 -> N)  |
    +------------------+     | (shrinks: N -> 2)|
                              +------------------+

Governor State Machine:

CPU > 80%           CPU < 30%
    |                    |
    v                    v
+----------+       +----------+
| SCALE UP | ----> | IDLE    |
+----------+       +----------+
                      ^
                      |
               CPU < 30%
               + cooldown
               |
               v
         +----------+
         |SCALE DOWN|
         +----------+

When to Use:

  • Production environments with variable session counts

  • ISPs with peak usage hours (evening, weekends)

  • Systems where "set and forget" scaling is desired

  • Any deployment with fluctuating load patterns

When Not to Use:

  • Predictable, constant load (use Basic Two-Worker Setup)

  • Very small systems with limited resources

  • Environments where frequent worker changes are disruptive

Configuration Files:

# /etc/rc.conf
pppoed_enable="YES"
pppoed_flags="-L -w 2 -G 0 em0"

# /etc/sysctl.conf
# Governor settings
net.graph.pppoe_lb.governor.enabled=1           # Enable governor
net.graph.pppoe_lb.governor.mode=1              # 1=auto mode
net.graph.pppoe_lb.governor.min_workers=2       # Never go below 2 workers
net.graph.pppoe_lb.governor.max_workers=0        # 0=auto (use mp_ncpus)

# Thresholds
net.graph.pppoe_lb.governor.cpu_threshold=80     # Scale up at 80% CPU
net.graph.pppoe_lb.governor.cpu_low_threshold=30  # Scale down at 30% CPU

# Timing (seconds)
net.graph.pppoe_lb.governor.scale_up_interval=10   # Min between scale-ups
net.graph.pppoe_lb.governor.scale_down_interval=60  # Min between scale-downs
net.graph.pppoe_lb.governor.drain_timeout=30       # Wait for drain before removal

What Each Setting Does:

SettingDefaultDescription

governor.enabled=1

0

Enable automatic scaling

governor.mode=1

1

Auto mode (vs manual mode)

min_workers=2

1

Never scale below this count

max_workers=0

0

0 means use mp_ncpus (CPU cores)

cpu_threshold=80

80

Add worker when CPU exceeds this %

cpu_low_threshold=30

30

Remove worker when CPU below this %

scale_up_interval=10

5

Seconds between scale-up decisions

scale_down_interval=60

60

Seconds between scale-down decisions

drain_timeout=30

30

Seconds to wait for worker drain

Governor Scaling Behavior:

Timeline Example:

Time    CPU     Workers  Event
----    ---     -------  -----
00:00   15%     2        Normal operation
00:15   85%     2        Scale UP triggered!
00:15   85%     3        Worker 2 created
00:30   70%     3        Stabilized
01:00   18%     3        Scale DOWN triggered!
01:00   18%     3        Worker 2 marked DRAINING
01:30   18%     2        Worker removed (drain complete)

"Change of Heart" Scenario:

The governor can cancel a pending worker removal if load increases:

Time    CPU     Workers  Event
----    ---     -------  -----
10:00   15%     3        Normal operation
10:10   12%     3        Scale DOWN triggered
10:10   12%     3        Worker 2 marked DRAINING
10:25   85%     3        Traffic spike! (evening peak)
10:25   85%     3        Scale UP needed - CANCEL DRAIN!
10:25   85%     3        Worker 2 returns to ACTIVE
10:25   85%     4        Worker 3 created (scale up)

Verification Steps:

# 1. Check governor is enabled and running
$ sysctl net.graph.pppoe_lb.governor.enabled net.graph.pppoe_lb.governor.mode
net.graph.pppoe_lb.governor.enabled: 1
net.graph.pppoe_lb.governor.mode: 1

# 2. View governor status
$ ngctl msg pppoe_lb:pppoed showgovernor
Governor Status:
  Enabled: Yes
  Mode: Auto
  Min Workers: 2
  Max Workers: 8
  Current Workers: 3
  Active Workers: 3
  Draining Workers: 0
  Pending Removals: 0
  Last Decision: none

# 3. Monitor for scaling events in logs
$ tail -f /var/log/messages | grep pppoe_lb
May  1 10:15:22 hostname ng_pppoe_lb: governor: scale up (reason: cpu_high)
May  1 10:15:22 hostname ng_pppoe_lb: created worker 2 (now 3 workers)

Expected Sysctl Output:

$ sysctl net.graph.pppoe_lb.governor
net.graph.pppoe_lb.governor.enabled: 1
net.graph.pppoe_lb.governor.mode: 1
net.graph.pppoe_lb.governor.min_workers: 2
net.graph.pppoe_lb.governor.max_workers: 0
net.graph.pppoe_lb.governor.cpu_threshold: 80
net.graph.pppoe_lb.governor.cpu_low_threshold: 30
net.graph.pppoe_lb.governor.scale_up_interval: 10
net.graph.pppoe_lb.governor.scale_down_interval: 60
net.graph.pppoe_lb.governor.current_workers: 3
net.graph.pppoe_lb.governor.active_workers: 3
net.graph.pppoe_lb.governor.draining_workers: 0
net.graph.pppoe_lb.governor.total_sessions: 156
net.graph.pppoe_lb.governor.last_decision: none
net.graph.pppoe_lb.governor.last_reason: none

Troubleshooting:

# Problem: Governor not scaling up
$ sysctl net.graph.pppoe_lb.governor.current_workers
net.graph.pppoe_lb.governor.current_workers: 2

# Check: Is governor enabled?
$ sysctl net.graph.pppoe_lb.governor.enabled
net.graph.pppoe_lb.governor.enabled: 0
# Solution: Enable it
$ sysctl net.graph.pppoe_lb.governor.enabled=1

# Check: Is CPU actually high?
$ sysctl net.graph.pppoe_lb.governor.cpu_usage
net.graph.pppoe_lb.governor.cpu_usage: 45
# CPU must exceed 80% (cpu_threshold) to scale up

# Check: At maximum workers?
$ sysctl net.graph.pppoe_lb.governor.max_workers
net.graph.pppoe_lb.governor.max_workers: 0
# 0 means mp_ncpus, check actual CPU count
$ sysctl mp_ncpus
mp_ncpus: 8
# Currently at 2 workers, max is 8 - should be able to scale

30.6.10.3. High-Load Setup

Purpose: For high-volume PPPoE servers expecting thousands of concurrent sessions. Uses more aggressive scaling parameters and higher session-per-worker targets.

Architecture:

                            +------------------+
                            |   pppoe_lb       |
    DSLAM <-- Ethernet -->  |   (dispatcher)   |<---> Worker 0
    em0                     |                  |<---> Worker 1
                            |                  |<---> ...
                            +------------------+<---> Worker 7

    Expected Capacity: 8 workers x 1000 sessions = 8000 concurrent sessions

When to Use:

  • High-volume PPPoE servers (1000+ concurrent sessions)

  • ISPs with thousands of subscribers

  • Systems with dedicated high-performance hardware

  • When predictable high throughput is required

When Not to Use:

  • Small deployments with <500 total sessions

  • Resource-constrained systems

  • Development or testing environments

Configuration Files:

# /etc/rc.conf
pppoed_enable="YES"
pppoed_flags="-L -w 8 -G 0 em0"

# /etc/sysctl.conf
# Governor settings
net.graph.pppoe_lb.governor.enabled=1
net.graph.pppoe_lb.governor.mode=1
net.graph.pppoe_lb.governor.min_workers=4       # Higher minimum for production
net.graph.pppoe_lb.governor.max_workers=0       # 0=auto (use mp_ncpus)

# Thresholds (more aggressive)
net.graph.pppoe_lb.governor.cpu_threshold=70     # Scale up earlier (70%)
net.graph.pppoe_lb.governor.cpu_low_threshold=40 # Scale down later (40%)
net.graph.pppoe_lb.governor.sess_per_worker=1000 # Target sessions per worker

# Timing (faster response)
net.graph.pppoe_lb.governor.scale_up_interval=5    # Faster scale-up (5s)
net.graph.pppoe_lb.governor.scale_down_interval=120 # Slower scale-down (2min)
net.graph.pppoe_lb.governor.drain_timeout=60        # Longer drain timeout

What Changed from Production Setup:

SettingProductionHigh-LoadWhy Changed

w (workers)

2

8

More capacity

min_workers

2

4

Never drop below 4

cpu_threshold

80

70

Scale up earlier

cpu_low_threshold

30

40

Scale down later

scale_up_interval

10

5

Faster response

scale_down_interval

60

120

Slower scale-down

drain_timeout

30

60

Longer drain for high-load

sess_per_worker

500

1000

Higher session density

Capacity Estimates:

Worker Count     Sessions (500/worker)  Sessions (1000/worker)
------------     ---------------------  ----------------------
4 workers       2,000                  4,000
8 workers       4,000                  8,000
16 workers      8,000                  16,000

Note: Actual capacity depends on:
  - Available CPU cores (max_workers = mp_ncpus)
  - Network interface throughput
  - Traffic patterns (session vs. bandwidth)
  - Available memory

Verification Steps:

# 1. Verify 8 workers started
$ sysctl net.graph.pppoe_lb.governor.current_workers
net.graph.pppoe_lb.governor.current_workers: 8

# 2. Check per-worker session distribution
$ for i in $(seq 0 7); do
    echo -n "Worker $i: "
    sysctl -n net.graph.pppoe_lb.workers.$i.sessions
  done
Worker 0: 134
Worker 1: 142
Worker 2: 128
Worker 3: 156
Worker 4: 139
Worker 5: 147
Worker 6: 131
Worker 7: 125

# 3. Verify balance (all should be within 20% of average)
$ sysctl net.graph.pppoe_lb.governor.avg_sessions_worker
net.graph.pppoe_lb.governor.avg_sessions_worker: 137

# 4. Check for load imbalance warnings
$ sysctl net.graph.pppoe_lb.health
net.graph.pppoe_lb.health.load_imbalance_pct: 12
# Values > 20% may indicate issues

Performance Monitoring:

# Monitor CPU usage per worker (top usage)
top -b -n 1 | grep pppoed

# Check total throughput
$ netstat -I em0 -b | head -5

# View per-worker statistics
$ ngctl msg pppoe_lb:pppoed showstats

# Check for errors
$ sysctl net.graph.pppoe_lb.stats.total_errors

Troubleshooting:

# Problem: Uneven session distribution
$ sysctl net.graph.pppoe_lb.workers.0.sessions
net.graph.pppoe_lb.workers.0.sessions: 456
$ sysctl net.graph.pppoe_lb.workers.1.sessions
net.graph.pppoe_lb.workers.1.sessions: 45

# Causes:
# 1. Sessions have session affinity (same MAC -> same worker)
# 2. Some workers are in DRAINING state
# 3. Hash algorithm is distributing unevenly

# Check for draining workers
$ sysctl net.graph.pppoe_lb.governor.draining_workers
net.graph.pppoe_lb.governor.draining_workers: 1
# Worker 1 might be draining!

# Solution: Check worker states
for i in $(seq 0 7); do
  echo "Worker $i: $(sysctl -n net.graph.pppoe_lb.workers.$i.state)"
done
Worker 0: 0 (ACTIVE)
Worker 1: 1 (DRAINING)  <-- This worker won't get new sessions
Worker 2: 0 (ACTIVE)
...

# To cancel draining:
$ sysctl net.graph.pppoe_lb.workers.1.state=0

30.6.10.4. Choosing a Configuration

Use this decision guide to select the appropriate configuration:

ScenarioBasicAuto-ScalingHigh-Load

CPU cores

2

4+

8+

Expected sessions

<500

100-2000

1000-8000+

Load pattern

Constant

Variable

High/Variable

Scaling needs

None

Automatic

Automatic

Monitoring

Manual

Governor

Governor

Startup time

Fast

Medium

Slow

Resource usage

Low

Medium

High

Transitioning Between Configurations:

You can safely transition between configurations:

# 1. Start with Basic (testing)
# 2. Move to Auto-Scaling when ready for production
# 3. Move to High-Load if capacity is exceeded

# To change configuration:
# 1. Edit /etc/sysctl.conf with new settings
# 2. Apply new sysctls: sysctl -f /etc/sysctl.conf
# 3. Adjust worker count: ngctl msg pppoe_lb:pppoed setworkers N
# 4. Or restart pppoed: service pppoed restart

30.6.11. pppoetest - Testing and Diagnostic Tool

The pppoetest tool provides comprehensive testing and diagnostic capabilities for the PPPoE load balancer. It can operate in diagnostic mode (observe existing connections) or create mode (generate test traffic).

30.6.11.1. Overview

pppoetest is designed for:

  • Diagnostic mode: Safely observe production systems without creating sessions

  • Accuracy testing: Verify session distribution and affinity

  • Transfer testing: Test file transfers with checksums

  • Governor testing: Verify auto-scaling behavior

Key features:

  • No external dependencies (part of FreeBSD base system)

  • Client-server architecture for distributed testing

  • JSONL output for integration with monitoring systems

  • Production-safe diagnostic mode (read-only by default)

30.6.11.2. Installation

pppoetest is built as part of the FreeBSD base system:

# Build the tool
$ cd /usr/src/usr.sbin/pppoetest
$ make
$ make install

# Verify installation
$ which pppoetest
/usr/sbin/pppoetest

30.6.11.3. Modes of Operation

ModeFlagDescription

Diagnose

-m diagnose

Passive observation of existing sessions (default)

Create

-m create

Actively create test sessions

Mixed

-m mixed

Observe existing and create new sessions

Server

-m server

Run as server for remote client testing

Client

-m client

Connect to server for distributed testing

30.6.11.4. Diagnostic Mode (Default)

Diagnostic mode is safe to run on production systems - it never creates sessions:

# Basic diagnostics (read-only, safe for production)
$ pppoetest -m diagnose

PPPoE Load Balancer Diagnostic Report
=====================================
Host: pppoe-server.example.com
Time: 2024-05-01 10:15:22 UTC

Workers: 3
  Worker 0: ACTIVE (234 sessions, 45m uptime)
  Worker 1: ACTIVE (256 sessions, 45m uptime)
  Worker 2: DRAINING (12 sessions, 10m uptime)

Sessions: 502 total
  Active: 490
  Draining: 12

Governor: Enabled
  CPU: 52%
  Last Decision: none

Health Score: 87/100 ✓

30.6.11.5. Health Checks

Run comprehensive health checks on the load balancer:

# Check health score
$ pppoetest -m diagnose --health-score

=== PPPoE Load Balancer Health Score: 87/100 ===

Component Scores:
  ✓ Worker Availability: 100/100
  ⚠ Load Distribution:   72/100 (max deviation: 28%)
  ✓ Error Rate:           100/100
  ✓ Session Affinity:     99/100 (1 drift detected)

Warnings:
  - Load distribution uneven (Worker 0 has 28% more than average)
  - 1 session affinity drift detected

Recommendations:
  - Investigate Worker 0 for potential issues
  - Check for sessions that moved between workers unexpectedly

Health Score Interpretation:

ScoreStatusMeaning

90-100

PASS

Healthy, no action needed

70-89

WARN

Minor issues, monitor closely

50-69

FAIL

Significant problems, investigate

0-49

CRITICAL

Immediate attention required

30.6.11.6. Load Balance Check

Check if sessions are evenly distributed:

$ pppoetest -m diagnose --check-balance

=== Load Balance Check ===
┌────────┬───────────┬──────────┬────────────────┐
│ Worker │ Sessions  │ Expected │ Deviation      │
├────────┼───────────┼──────────┼────────────────┤
│ 0      │ 234       │ 200     │ +17% ⚠         │
│ 1      │ 256       │ 200     │ +28% ⚠         │
│ 2      │ 112       │ 200     │ -44% ⚠         │
└────────┴───────────┴──────────┴────────────────┘

Imbalance: 28% (threshold: 20%)
Status: ⚠ WARNING - Load distribution exceeds threshold

Recommendations:
  - Worker 2 may be in DRAINING state
  - Check worker states: sysctl net.graph.pppoe_lb.workers.*.state

30.6.11.7. Session Affinity Check

Verify sessions stay on their assigned workers:

$ pppoetest -m diagnose --check-affinity

=== Affinity Check ===
┌────────────┬────────────────┬─────────────────┬────────────┐
│ Session ID │ Expected Worker│ Actual Worker  │ Status     │
├────────────┼────────────────┼─────────────────┼────────────┤
│ 0x7f3a2b  │ 2              │ 2               │ ✓ OK       │
│ 0x8c4d1e  │ 1              │ 1               │ ✓ OK       │
│ 0x9a1f7c  │ 3              │ 2 ⚠             │ ✗ DRIFT    │
└────────────┴────────────────┴─────────────────┴────────────┘

Total: 3 sessions checked
  ✓ Passed: 2
  ✗ Failed: 1 (session drift detected)

Session drift may indicate:
  - Worker was removed while session was active
  - Hash algorithm rebalancing
  - Race condition in session routing

30.6.11.8. Worker State Check

View detailed worker status:

$ pppoetest -m diagnose --show-workers

=== Worker Status ===
┌────────┬─────────┬──────────┬───────────┬───────────┬──────────┐
│ Worker │ State   │ Sessions │ Bytes In  │ Bytes Out │ Uptime   │
├────────┼─────────┼──────────┼───────────┼───────────┼──────────┤
│ 0      │ ACTIVE  │ 234      │ 1.2 GB    │ 456 MB    │ 45m      │
│ 1      │ ACTIVE  │ 256      │ 1.4 GB    │ 523 MB    │ 45m      │
│ 2      │ DRAINING│ 12       │ 89 MB     │ 34 MB     │ 10m      │
└────────┴─────────┴──────────┴───────────┴───────────┴──────────┘

Worker States:
  0 = ACTIVE (accepting new sessions)
  1 = DRAINING (completing existing sessions)
  2 = PENDING_REMOVAL (ready for removal)

30.6.11.9. Real-Time Monitoring

Monitor the load balancer continuously:

# Monitor every 5 seconds
$ pppoetest -m diagnose --monitor --interval 5

Press Ctrl+C to stop

┌──────────────────────────┬────────┬────────┬────────┬────────┐
│ Metric                   │ Now    │ 5s ago │ 30s ago│ 5m ago │
├──────────────────────────┼────────┼────────┼────────┼────────┤
│ Total Sessions           │ 800    │ 798    │ 785    │ 750    │
│ Active Workers           │ 3      │ 3      │ 3      │ 2      │
│ Sessions/Worker (avg)   │ 267    │ 266    │ 262    │ 375    │
│ CPU Usage (avg)        │ 52%    │ 48%    │ 45%    │ 38%    │
└──────────────────────────┴────────┴────────┴────────┴────────┘

Changes detected:
  ↑ Workers: 2 -> 3 (governor scaled up)
  ↑ Sessions: 750 -> 800 (+50 in 5 minutes)

30.6.11.10. Client-Server Mode

For distributed testing across multiple systems:

Server (test coordinator):

# Start server on test coordinator
$ pppoetest -m server -i em0 -p 9001

Server listening on port 9001
Waiting for client connections...

Client (test initiator):

# Connect to server and run tests
$ pppoetest -m client -h test-coordinator.example.com -p 9001

Connected to test-coordinator.example.com:9001
Running accuracy test...

When both sides are connected:

Both sides will see coordinated test results:

┌─────────────────┐    ┌─────────────────┐
│   CLIENT SIDE   │    │   SERVER SIDE   │
│ Blocks: 800     │    │ Blocks: 800    │
│ Checksum: OK    │    │ Checksum: OK    │
└─────────────────┘    └─────────────────┘
        ↓                      ↓
   VERIFICATION (MATCH/MISMATCH)

✓ Transfer test PASSED
  - 800 blocks transferred
  - Checksums match on both sides

30.6.11.11. Output Formats

pppoetest supports multiple output formats for integration with other tools:

# Console output (default)
$ pppoetest -m diagnose --format console

# JSON output (for parsing)
$ pppoetest -m diagnose --format json
{
  "type": "diagnostic_report",
  "timestamp": "2024-05-01T10:15:22Z",
  "workers": 3,
  "sessions": 502,
  "health_score": 87,
  "status": "warn"
}

# JSONL output (streaming, for log processors)
$ pppoetest -m diagnose --format jsonl
{"type":"worker_status","id":0,"state":"ACTIVE","sessions":234}
{"type":"worker_status","id":1,"state":"ACTIVE","sessions":256}
{"type":"health_score","score":87,"status":"warn"}

# CSV output (for spreadsheets)
$ pppoetest -m diagnose --format csv
worker_id,state,sessions,bytes_in,bytes_out
0,ACTIVE,234,1288490188,478150656
1,ACTIVE,256,1503238553,548453324
2,DRAINING,12,95787918,36372403

# TAP output (for test frameworks)
$ pppoetest -m diagnose --format tap
1..4
ok 1 - Worker 0 is ACTIVE
ok 2 - Worker 1 is ACTIVE
ok 3 - Worker 2 is DRAINING
ok 4 - Health score >= 70

30.6.11.12. Configuration Options

Key command-line options:

OptionDefaultDescription

-m mode

diagnose

Operation mode

-i iface

em0

Network interface

-h host

localhost

Server hostname (client mode)

-p port

9001

Control port

-f format

console

Output format

--interval N

5

Monitoring interval (seconds)

--balance-threshold N

20

Imbalance warning threshold (%)

--health-threshold N

40

Health error threshold (%)

--no-color

false

Disable colored output

30.6.11.13. Exit Codes

CodeMeaning

0

Success, all tests passed

1

One or more tests failed

2

Usage error (invalid arguments)

3

Connection error (server unreachable)

4

Timeout waiting for response

5

Internal error

30.6.11.14. Examples

30.6.11.14.1. Basic Diagnostic

Check system health without creating traffic:

$ pppoetest -m diagnose
30.6.11.14.2. Check Balance

Verify session distribution is within tolerance:

$ pppoetest -m diagnose --check-balance --balance-threshold 15
# Will warn if deviation exceeds 15%
30.6.11.14.3. Continuous Monitoring

Monitor system with 2-second intervals:

$ pppoetest -m diagnose --monitor --interval 2
# Press Ctrl+C to stop
30.6.11.14.4. Remote Diagnostic

Diagnose a remote system:

$ pppoetest -m diagnose -h pppoe-server.example.com
30.6.11.14.5. JSONL for Logstash

Stream diagnostics to a log processor:

$ pppoetest -m diagnose --format jsonl | jq .
{"type":"worker_status","id":0,"state":"ACTIVE","sessions":234,"timestamp":"..."}
{"type":"worker_status","id":1,"state":"ACTIVE","sessions":256,"timestamp":"..."}
{"type":"health_score","score":87,"status":"warn","timestamp":"..."}

30.6.12. See Also


Last modified on: May 1, 2026 by Mark LaPointe