Connecting Two Unix Systems Using Point-To-Point Protocol (Quick Version)

I had (have?) a detailed discussion of how to use pppd (Point-To-Point Protocol Daemon) in the pipeline. However, Twitter friend Gravis posted a very nice article on how PPP the protocol works, and I felt compelled to convert my original detailed post into a more concise form!

The Point-To-Point Protocol (PPP) is a Layer 2 protocol that is meant to connect computers in a network, like Ethernet or Wifi. However, unlike Ethernet and Wifi, PPP doesn't specify how voltages should be sent from one computer to another. The Fundamentals section in Gravis' article provides great detail as to the rationale. An important consequence of PPP not specifying how voltages are sent is that lots of computing hardware can be used to send and receive PPP packets, such as RS-232 ports.

This article will focus on sending/receiving PPP packets over Universal Asynchronous Receiver Transmitter (UART). Due to their simplicitly, UARTs are extremely common hardware for communications and debugging on Systems-on-a-Chip (SoCs), FPGAs, Single-Board Computers (SBCs), and just many computers in general. Considering their proliferation, a UART becomes a powerful communications interface when combined with PPP and TCP/IP; you now have a means to connect computers without any other I/O interfaces to the rest of the TCP/IP-speaking Internet if you so choose!

The Point-To-Point Protocol Daemon

One common PPP implementation is the Point-To-Point Protocol Daemon for Unix systems, or pppd. pppd provides both a daemon (service) and kernel component to create a network interface that can send TCP/IP packets and PPP packets to a UART

You can use pppd to set up two *nix computers to communicate to each other over their own local TCP/IP network. In fact that's a major characteristic of a Point To Point network! In such a setup, both computers can contact each other via applications like ssh, but are blissfully unaware of any other computers that exist in the world.

In the absence of any dedicated networking hardware, you can still use pppd on Unix systems to connect systems to the Internet. This article will describe how to use pppd to connect to the Internet proper, using my current setup at home as an example. My setup should be applicable to most use cases, as long as one of the computers in your PPP links has another network interface besides UART, such as an Ethernet port or Wifi adapter.

My Setup

Picture of my TinkerBoard and Raspberry Pi connected to each other via UART.

In the above image, my TinkerBoard (right) and Raspberry Pi (left) are connected to each other by three wires. Green connects TinkerBoard RX to RPi TX, white connects TinkerBoard TX to RPi RX, and black connects both boards' grounds to each other. The remaining wires implement soft reset switches and are not currently wired up properly.

The ribbon cables on both boards are Adafruit downgrade ribbon cables to connect the GPIO header to a breadboard out-of-shot. The hat on the TinkerBoard is a Perma-Proto hat to break out two UARTs. The hat on the on the RPi is custom perfboard also breaking out the UART and reset pins; I only had one Perma-Proto hat :).

I have two Single-Board Computers (SBCs) deployed at present- an ASUS TinkerBoard running a Debian variant, and a Raspberry Pi 1 Model A+. The TinkerBoard SoC has 5 UARTs, an Ethernet port, and a Wifi adapter/antenna. 4 UARTs are exposed on GPIO headers. I use one of the additional UARTs exclusively for PPP. The Raspberry Pi has only a single UART. This is multiplexed between a serial console and PPP session.

UARTs are often exposed as three or more pin headers on your computer's PCB- receive (RX), transmit (TX), and ground (GND).

If you don't have a TinkerBoard and RPi, the important part of my setup is that the TinkerBoard has multiple pieces of hardware for network interfaces- Wifi, Ethernet, and UART- while my RPi has only one- a UART. By routing UART traffic between two computers via PPP to/from other existing networking hardware, it is possible to connect the UART-only computer to the Internet.

My config file settings below can be duplicated for many other pieces of hardware, as long as the above condition of "at least one of the computers has more than a UART for networking" is met.

TinkerBoard and Debian

/etc/ppp/options.ttyS1 Config

Besides the command line, pppd initializes itself using a configuration file containing options under the /etc/ppp directory. Options file names take the form of options.$DEVICE, where $DEVICE represents the device name of your serial port. You will need to know the names that your Unix system assigned your serial ports before setting up pppd. In my case, I have allocated UART2 on the TinkerBoard GPIO header specifically for the PPP link. This is called ttyS1 by Linux 1 .

I have posted my /etc/ppp/options.ttyS1 config file in full below, and I will briefly explain what each options entails after the config file. If you want more information, I encourage you to consult the pppd man page, which details all possible options that pppd understands on the command line or in a config file:

noauth
local
nodetach
nocrtscts
xonxoff
192.168.1.164:192.168.1.165
persist
proxyarp

Aside

IP Forwarding

Any traffic that's meant for the RPi from one of the other interfaces needs to somehow reach the UART interface with the PPP link. pppd itself only does Proxy ARP for you, and (as far as I understand) will not check other interfaces for packets meant for one of the two computers reachable on the UART interface. Instead, you must ask the OS to forward packets for you from one interface to another, effectively turning one of your computers with a PPP link into a router.

Normally, Linux will not forward packets from one interface to another by default. You have two options:

Note that IP forwarding is not required to set up a Point-To-Point link.

/etc/systemd/system/rpippp.service Unit File

[Unit]
Description=PPP
Wants=network-online.target
After=network-online.target
[Service]
Type=idle
ExecStart=/usr/sbin/pppd -d /dev/ttyS2 576000
Restart=always
TimeoutStartSec=120
[Install]
WantedBy=multi-user.target
Alias=ppp.service

Since my TinkerBoard is running a Debian variant, it also comes with systemd. So we create a unit file for our pppd service.

Raspberry Pi Model A+ and NetBSD

Because my Raspberry Pi is an original Model A+, it doesn't have an Ethernet port. This means that short of using a USB Wifi adapter (which I find flaky), using the UART is one of the only ways to connect to Internet. However, there's only one UART on the Raspberry Pi (I don't know at present how to use the mini UART), and I need that UART occassionally for a serial console for debugging.

As it turns out, there's nothing preventing me from using the same UART for a serial console and a PPP connection a long as I don't try to use pppd and a serial console at the same time over the same UART.

NetBSD comes with pppd as part of the base set tarball, so there is no bootstrapping problem with needing to download pppd before using pppd. If pppd wasn't part of the base distribution, I would use a separate computer to download the pppd package for your distro and then transfer it via flash drive ("sneakernet") or use Kermit over the UART via a USB-to-serial cable connection.

NetBSD getty

The getty implementation provided with NetBSD is capable of multiplexing normal login via a serial console with a PPP session. A Linux getty with such capability may be coming soon.

/etc/gettytab

# PPP network link login
#
# these entries can be used by ISPs or others who want to be able
# to offer both a "shell" and a PPP login on the same port. Setting
# the "pp" attribute allows getty(8) to recognize a PPP link start
# negotiation, and invoke the program listed, in addition to normal
# login(1).
#
# N.B.: if PPP is recognized, this bypasses normal login/password
# exchange; the expectation is that you'll configure pppd (or whatever)
# to require a PAP or CHAP handshake for authentication after PPP is
# started up.
#
# It is also recommended that you use hardware (CTS/RTS) flow control
# on the port, and run the port as fast as possible, to allow modems
# extra time to do data compression, if enabled.
#
ppp:np:ce:ck:pp=/usr/sbin/pppd:
#
ppp.19200|PPP-19200:sp#19200:tc=ppp:
ppp.38400|PPP-38400:sp#38400:tc=ppp:
ppp.57600|PPP-57600:sp#57600:tc=ppp:
ppp.115200|PPP-115200:sp#115200:tc=ppp:
ppp.230400|PPP-230400:sp#230400:tc=ppp:
ppp.460800|PPP-460800:sp#460800:tc=ppp:
ppp.500000|PPP-500000:sp#500000:tc=ppp:
ppp.576000|PPP-576000:sp#576000:tc=ppp:

/etc/ttys

#       $NetBSD: ttys,v 1.8 2019/09/25 23:09:21 abs Exp $
#
#       from: @(#)ttys  5.1 (Berkeley) 4/17/89
#
# name  getty                           type    status          comments
#
console "/usr/libexec/getty ppp.576000" vt100   on secure
constty "/usr/libexec/getty default"    vt100   off secure
ttyE0   "/usr/libexec/getty Pc"         wsvt25  off secure
ttyE1   "/usr/libexec/getty Pc"         wsvt25  off secure
ttyE2   "/usr/libexec/getty Pc"         wsvt25  off secure
ttyE3   "/usr/libexec/getty Pc"         wsvt25  off secure
tty00   "/usr/libexec/getty default"    unknown off secure
tty01   "/usr/libexec/getty default"    unknown off secure
tty02   "/usr/libexec/getty default"    unknown off secure
tty03   "/usr/libexec/getty default"    unknown off secure
tty04   "/usr/libexec/getty default"    unknown off secure
tty05   "/usr/libexec/getty default"    unknown off secure
tty06   "/usr/libexec/getty default"    unknown off secure
tty07   "/usr/libexec/getty default"    unknown off secure

/etc/ppp/options.console Config

Now, we need to set up a config file on the RPi side of things. It is perfectly fine to use console (as in /dev/console) as the serial device name. NetBSD will assign /dev/console to the UART exposed on the GPIO header, which is what we want.

ipcp-accept-local
noauth
local
xonxoff
defaultroute

Here areshort explanations on new options that pppd needs for this scenario where the RPi spawns pppd instances when it detects PPP activity from the TinkerBoard:

/etc/resolv.conf

# Generated by resolvconf
domain $DOMAIN
nameserver $NAMESERVER

One of the computers in your PPP links needs to have another network interface besides a UART.

Connecting Your UART-Only Board to the Internet.

If you want to set up two *nix computers to communicate to each other via pppd, you're all set using the above steps! However, you probably want to access the Internet proper from a computer that only has a UART (e.g. no Ethernet or Wifi) for commmunication with the outside world. In this case, a bit of extra setup is required.

Next Steps

Tuning Speed

After you've verified

Baud RateData CopiedSpeed
230400524288 bytes22.1 kB/s
460800524288 bytes41.2 kB/s
500000524288 bytes40.9 kB/s
576000524288 bytes44.3 kB/s
921600524288 bytes44.1 kB/s
1000000524288 bytes29.9 kB/s

UARTs are not designed for high speeds, emitting voltages over a single-ended interface suspectible to noise. However, the speeds you can get (tens of kBps) are acceptable for a number of background tasks and downloading software.

Enjoy Your New Internet Connection!

For better or worse, connecting a computer to the outside world is a prerequisite for making the computer useful over long periods of time. While there is a lot of work to get data from source to destination

Troubleshooting

The following section briefly discusses problems I have personally run into when getting a PPP link between my TinkerBoard and RPi to work.

TinkerBoard ceases all communications with the RPi.

Make sure your pppd options file contains local on both the TinkerBoard and RPi. It is possible that without this option, pppd on the TinkerBoard will wait for hardware flow control signals that will never arrive.

I can't ssh into the RPi from a remote computer besides the TinkerBoard.

Make sure IP forwarding is enabled on the TinkerBoard.

I can't access the Internet via domain names on the RPi.

Make sure your nameserver in resolv.conf is set up properly. If using DHCP, this is taken care of for you. However, pppd setup falls under "static" IP, and while it handles most static IP configuration steps via its config file, setting up your nameserver is not one of those steps.

I think this is a NetBSD issue; NetBSD uses the break condition (abnormally long low voltage) to start the kernel debugger. Any noise on the line during TinkerBoard and/or NetBSD boot could create that condition, causing the RPi to stop responding. And my own experience is that pppd doesn't tolerate the boot messages sent from the RPi well, seeing those messages as garbage. Therefore I recommend not starting the pppd link at boot via systemctl enable. Rather, wait until you need to access the other end of the UART after boot has completed on both ends of the link, and then run systemctl start rpippp.service.

Footnotes

1 TinkerBoard UART naming is confusing. The RockChip that powers the TinkerBoard has 5 UARTs. From reading the manual and comparing pinouts, UART0 is not exposed on the GPIO header and isn't assigned a device node. So UART1 becomes ttyS0, UART2 becomes ttyS1, etc. I'll need to look into this again at some point, because I remember UART0 at some point in the past getting a device node.