Adding Features to Dial-Up PPP Service
I operate a small web hosting service, specializing in services for the arts community and for small local businesses. As is customary, I offer a variety of support services to all my clients, including the ability to dial directly to my server and connect to the Internet using the point-to-point protocol (PPP). All services, including PPP access, are provided by a venerable and extremely reliable 486 running the latest production release of the Linux kernel.
Having spent a couple of years helping to build one of Austin's first Internet service providers during the early days of the Internet, I am familiar with the process of using UNIX to find original solutions to unsolved problems. At that time, there were no Portmasters, and dial-up access was provided by UNIX boxes, Digiboards and clever programming. Many solutions and tools which are commonplace today were unknown or only experimental then.
Modern releases of Linux offer a good deal more to work with than did the early non-Linux kernels; nevertheless, when I developed my own dial-up access, I found that while all parts of my dial-up system were available, several functions important to me were not available in the published packages. In particular, I wanted to offer full Internet access as a subscription service. Customers who chose not to subscribe to this service would still have full access via PPP dial up to the file spaces on their local web site using FTP or http. I also needed a way to implement session timeouts based on inactivity. While all my customers are above average in terms of their sense of responsibility in such matters, everyone has attention lapses from time to time, walking away from an on-line session without logging off and hanging up.
The software I found readily available was the excellent mgetty+sendfax package by Gert Doering and the Debian/GNU distribution of pppd. mgetty manages communication with a modem and provides essential login functions, while pppd manages PPP protocol issues. Recent versions of mgetty (I am using mgetty-0.99.2) are capable of detecting incoming attempts at PPP negotiation and, if properly configured, will invoke pppd with a variety of options, passing over to it the responsibility for user authentication.
While this combination is quite flexible, it is not a totally integrated solution. pppd reads and takes setup instructions from user .ppprc files, and my first thought was that setting up a read-only, root-owned .ppprc file for each customer would give me the flexibility I needed to provide the Internet access permissions and limitations I wanted on a per-account basis. Unfortunately, the situation is not quite this simple. Because mgetty runs and invokes pppd as root, the only .ppprc file which pppd reads is ~root/.ppprc. User identification and authentication takes place after the ~/.ppprc file is read—not very useful for my purpose.
Fortunately, pppd provides a very nice open-ended hook in the form of two built-in script calls: ip-up (Listing 1 in the archive file) which is executed immediately after the network control protocol (IPCP) for PPP has come up, and ip-down (Listing 2 in the archive file) which is executed immediately after the link has gone down. Both scripts are provided with the interface name, tty device, speed, local IP address and remote IP address as command parameters, and from these, almost everything one might need to know about a PPP session can be discovered. Both ip-up and ip-down run with a real and effective user ID of root, eliminating any potential problems with user-owned processes executing system commands. Because I could do my per-user configurations from a single script, I could localize all my user information in a single data file.
To grant full Internet access to a pppd dial-up client, pppd invokes a technique called proxy ARP. A host using proxy ARP advertises a dial-up client's IP address linked to its own Ethernet interface address. IP traffic destined for the dial-up client is therefore sent to the host, which dutifully forwards it via the PPP interface. pppd can be configured on invocation to set up proxy ARP, either on the command line or in any one of its several configuration files, including the user .ppprc file. Proxy ARP can also be set up “manually” using the arp command to manipulate the kernel's ARP cache. Because none of the pppd configuration files can be used to distinguish one user from another, I chose to set up proxy ARP using a shell invocation of arp in the ip-up Perl script.
Using the arp command to set up proxy ARP requires two pieces of information: the IP address assigned to the dial-up client and the machine address of the Ethernet interface to which packets should be delivered. The dial-up IP address is passed as a parameter to ip-up. The hardware address of the appropriate Ethernet interface can be obtained from a couple of sources, but the easiest way to is to parse the information returned from ifconfig. This address can also be hard coded into the script, since it is not likely to change in the short term.
For ip-up to know whether to set up proxy ARP, it must know the identity of the user for whom it was invoked. Although the identity of the current user is not one of the items provided to ip-up by pppd, the name of the connecting tty device is available and associated with a user name in the system's utmp file. Invoking who provides a conveniently formatted table, which can be parsed to obtain the name of the user currently connected to any tty device. I use the user name as an index into a small flat file, /etc/ppp/proxyarp, which consists of a series of lines of tab-delimited data pairs, each pair consisting of a user name and either a “+” or a “-” indicating whether to set up proxy ARP for that particular user. With this information, ip-up has everything it needs to set up proxy ARP for a session and determine if appropriate to do so.
One “gotcha” which must be addressed in this scheme is ARP caching by the LAN gateway. The Cisco 750 series router which I use is reluctant to provide any information on or means of manipulating its internal ARP cache, and the default timeout (about five minutes) means that any connection made within this timeout period after a previous call will inherit the packet routing of the previous connection. While it is not a serious problem for me if my non-Internet users occasionally get full Internet access, a busy ISP would need to be able to exercise tighter control in this matter.
Monitoring session time and network activity is relatively easy under Linux. All the necessary information on packet traffic through each interface is made available by the Linux kernel in the pseudo-file /proc/net/dev, laid out as follows in a format that is both easy to read and easy to parse (see Listing 4). Inactivity timeouts can be triggered by the number of packets received, packets transmitted or a combination of these.
My timeout mechanism uses both the ip-up and ip-down scripts and a third Perl script, timeout.pl (Listing 3 in the archive file), which runs from the root crontab file every five minutes. ip-up creates a session file, /var/run/pppn.session (where n in pppn designates the appropriate interface). This file contains six fields:
The user name of the account owning the session (for logging and notification)
The process ID of the ppid process
The time the session began
The time activity was last observed on the interface
The total number of packets received on the interface
The total number of packets transmitted to the interface
timeout.pl reads the session file for each PPP interface each time it runs. If the total session time has not been exceeded, it checks the traffic on the interface. If it observes activity, it records the time and traffic statistics and rewrites the session file. If no traffic has occurred since the last check, the script checks the time since traffic was last observed and exits if the inactivity timeout has not been exceeded. If either of the timeout times has been exceeded, the script sends a SIGINT signal to the pppd process, causing it to execute an orderly hang up, which includes execution of the ip-down script. ip-down deletes the session file and any proxy ARP entry for the interface currently in the ARP cache.
With the exception of the reluctant router ARP cache noted above, this system works quite well in all respects. I have included optional e-mail notification in timeout.pl, so it sends me e-mail whenever a timeout occurs. I can also force a timeout by executing timeout.pl manually with a -t or -i option. Adding system logging of timeout events is on my “to do” list, but should be a relatively simple matter.