Workings of a Virtual Private Network in Linux—Part 2

More about securing our communication with the Internet.

Part 1 of this article described the theory. Now let's pick up the VPN mini-HOWTO, study the script that creates the VPN, run it, and explain what we see. The HOWTO (, text version, or is required reading for this article. The script, by Miquel van Smoorenburg and Ian Murdock, is in section 4.10 of the HOWTO.

The script runs on the local VPN server. If you configure reciprocally, you can arrange to let it run from either side with functionally equivalent results. What I call local/remote, Arpad Magosanyi calls master/slave. For working purposes, ssh needs a remote user account under which to log in. So, a remote user “slave”, which doesn't correspond to any human user, is first created and configured (permissions, keys).

The heart of the script embodies running pppd and route as described in Part 1 (section entitled The Network). With simplifications, here it is. Line 33 is the centerpiece.

VPN HOWTO script:

line  1:  #! /bin/sh
line 19:  MYPPPIP=
line 20:  TARGETIP=
line 21:  TARGETNET=

PPPD on both machines:

line 33:  /bin/pty-redir /usr/bin/ssh -o\
  'Batchmode yes' -t -l slave\
  /usr/local/bin/sudo /usr/sbin/pppd >/tmp/device
line 34:  TTYNAME='cat /tmp/device'
line 39:  /usr/sbin/pppd $TTYNAME\
ROUTE on both machines:
line 45:  route add -net $TARGETNET gw $TARGETIP
line 46:  /usr/bin/ssh -o 'Batchmode\
yes' -l slave\
/usr/local/bin/sudo /home/slave/sshroute
I numbered the lines, then deleted extraneous ones. For readability, I also substituted shell variables and changed their values to reflect real file and path names on my Red Hat 5.1 servers. Ignore sudo in line 33—it is nothing more than a command filter/authenticator, a mechanism you set up to allow some commands to run and disallow others. It is here for additional security. It is set up to permit pppd (HOWTO section 4.9), so its absence from line 33 would not affect operations. When you read line 33, you should see
ssh -t -l slave pppd
This says, “get logged into the remote machine under its user account 'slave' and run pppd; make the remote machine set up a pseudo-terminal (-t) as the destination for output of this pppd process.”

Doing It by Hand

Please note that in section 6 of the HOWTO, “Doing it by hand”, unlike the script, -t is absent from the command line. When I first did it by hand, I was unsuccessful at getting the desired “garbage right into [my] face” until I added -t. I got it going by using the following machine Internet addresses:

local machine's public IP:
remote machine's public IP:

The screen capture from the local machine was:

[root@localhost /root]# ssh -t  -l slave
~˜}#A!}!}!} }2}!}$}"(}%}&} } öŒ}'}"}(}""q~~˜}#A!}
!}!} }2}!}$}"(}%}&} } öŒ}'}"}(}""q~~˜}#A!}!}!}
}2}!}$}"(}%}&} } öŒ}'}"}(}""q~~˜}#A!}!}!} }2}!}$}
"(}%}&} } öŒ}'}"}(}""q~~˜}#A!}!}!} }2}!}$}"
(}%}&} } öŒ}'}"}(}""q~~˜}#A!}!}!} }2}!}$}"(}%}&}
} öŒ}'}"}(}""q~~˜}#A!}!}!} }2}!}$}"(}%}&} } öŒ}'}
"}(}""q~~˜}#A!}!}!} }2}!}$}"(}%}&} } öŒ}'}"}
(}""q~~˜}#A!}!}!} }2}!}$}"(}%}&} } öŒ}'}"}
(}""q~~˜}#A!}!}!} }2}!}$}"(}%}&} } öŒ}'}"}
Connection to closed.
[root@localhost /root]#
and the simultaneous remote-machine log entries were:
Nov  7 20:17:19 localhost sshd[1403]:
log: Connection from port 1023
Nov  7 20:17:21 localhost sshd[1403]:
log: RSA authentication for slave accepted.
Nov  7 20:17:22 localhost sshd[1405]:
log: executing remote command as user slave
Nov  7 20:17:22 localhost pppd[1405]:
pppd 2.3.3 started by slave, uid 507
Nov  7 20:17:22 localhost kernel:
registered device ppp1
Nov  7 20:17:22 localhost pppd[1405]:
Using interface ppp1
Nov  7 20:17:22 localhost pppd[1405]:
Connect: ppp1 <--> /dev/ttyp0
Nov  7 20:17:52 localhost pppd[1405]:
LCP: timeout sending Config-Requests
Augmenting the command with the author's pty-redir in the next step produced:
[root@localhost /root]# /bin/pty-redir
/usr/bin/ssh -t  -l slave  /usr/sbin/pppd
/dev/ttyp0[root@localhost /root]#
root@localhost /root]#
(where did the garbage go?) and simultaneous remote-machine log entries:
Nov  7 20:21:43 localhost sshd[1406]:
log: Connection from port 1023
Nov  7 20:21:46 localhost sshd[1406]:
log: RSA authentication for slave accepted.
Nov  7 20:21:46 localhost sshd[1408]:
log: executing remote command as user slave
Nov  7 20:21:46 localhost pppd[1408]:
pppd 2.3.3 started by slave, uid 507
Nov  7 20:21:46 localhost pppd[1408]:
Using interface ppp1
Nov  7 20:21:46 localhost pppd[1408]:
Connect: ppp1 <--> /dev/ttyp0
Nov  7 20:22:16 localhost pppd[1408]:
LCP: timeout sending Config-Requests
So far, it's working. Tracing through the log, sshd hears ssh. sshd agrees to run the command requested by ssh (since I preconfigured keys across the machines). The requested pppd command is then run, which prepares to use an interface called ppp1 and associates it with pseudo-terminal /dev/ttyp0. The process stopped there only because pppd was never run from the other end; however, everything here on the remote side went right. We'll see the process consummated below when we run the full-blown script to completion.

What's the difference between these two invocations? On the remote side, nothing; the logs are the same. On the local side, in the second invocation, the entire prior command string was fed to pty-redir and executed under its control, resulting in the garbage going away, it seems. Actually, it just went elsewhere; pty-redir “redirects” it. pty-redir is a short C language program by Mr. Magosanyi. It identifies a pseudo-terminal device that is not in use and opens it. Then it forces standard output—normally directed to the console—to the pseudo-terminal instead. Anything the program (ssh) would send to the console gets diverted to the pseudo-terminal device.

Don't confuse this pseudo-terminal with the one created by the -t option—they're on different machines. ssh -t makes sshd on the remote side create a pseudo-terminal over there, whereas pty-redir operates on the local side. By number, both pseudo-terminals happen to be /dev/ttyp0 this time, but that won't always be so. The local pseudo-terminal is manifested above in the screen output, the remote one in the log.

While it's nice to see the “garbage in our face” for diagnostic purposes, it's better to keep it out of sight for production purposes. That's why pty-redir was written. The pseudo-terminal will prove convenient as the “receiving vessel” for the incoming pppd output stream. We can launch a local pppd into it in order to create the desired connection, instead of having to do it more intrusively at our real terminal.