|
Linux Router Guide - UpdateSince publishing my first article about a Linux based internet router a lot of things have changed. For starters, I live on a different continent now. I have switched the internet service provider on several occasions. I went from dial-up first to ISDN and then to DSL, first volume based, then flat rate. Also the hardware and software configuration changed over time... |
The router hardware now consists of an i440BX board featuring a P3-1GHz supported by 512 MB RAM and a 27 GB harddisk. After moving house, the hardware is now fixed to the wall in a cupboard, see the picture:

In the past I used to burn bootable CD-ROMs containing the root file system. However, for ease of maintenance I now skip the final step of the production process and use the CD master on the production harddisk directly, mounted read-only. The remaining almost 27 GB of the harddisk are used for caching. The harddisk is wall-mounted using special rubber elements to decouple noise and vibration from the wall. This and the position of the cupboard outside the living areas means the operation of a harddisk is completely noise-free, thus removing one of the reasons for operating a diskless system.
As before, the construction of a diskless (or root read-only) router can be broken into distinct stages:
Well, without further ado, here we go into the details.
In principle, you can use whatever distro you are familiar with and which you can really tailor down to your needs in the second step. In fact, the tailoring already starts with the setup, since you do not want to install anything that is not needed. Complexity compromises security.
In my case, I stuck with Slackware since I have some experience with it, and it comes as a much less complex distro to start with. During installation I tried to minimize the number of installed packages. For those of you interested: the following is the content of my /var/log/packages directory which under Slackware contains the list of installed packages:
root@conner:~# ls /var/log/packages/ aaa_base-11.0.0-noarch-2 inetd-1.79s-i486-7 aaa_elflibs-11.0.0-i486-9 iproute2-2.6.16_060323-i486-1 at-3.1.10-i486-1 iptables-1.3.5-i486-2 autoconf-2.60-noarch-1 less-394-i486-1 automake-1.9.6-noarch-1 libtermcap-1.2.3-i486-6 bash-3.1.017-i486-1 libtool-1.5.22-i486-1 bin-11.0-i486-3 lilo-22.7.1-i486-2 bin86-0.16.15-i486-1 lsof-4.76-i486-1 binutils-2.15.92.0.2-i486-3 make-3.81-i486-1 bison-2.1-i486-1 man-1.6c-i486-2 byacc-1.9-i386-1 mkinitrd-1.0.1-i486-3 bzip2-1.0.3-i486-3 module-init-tools-3.2.2-i486-2 coreutils-5.97-i486-1 ncurses-5.5-i486-1 cxxlibs-6.0.3-i486-1 openssl-solibs-0.9.8d-i486-1 dcron-2.3.3-i486-5 perl-5.8.8-i486-3 devs-2.3.1-noarch-25 pkgconfig-0.21-i486-3 diffutils-2.8.1-i486-3 pkgtools-11.0.0-i486-4 e2fsprogs-1.38-i486-2 procps-3.2.7-i486-1 elvis-2.2_0-i486-2 proftpd-1.3.0-i486-1 etc-11.0-noarch-2 sed-4.1.5-i486-1 findutils-4.2.28-i486-1 shadow-4.0.3-i486-13 flex-2.5.4a-i486-3 shared-mime-info-0.19-i486-1 gawk-3.1.5-i486-3 strace-4.5.14-i486-1 gcc-3.4.6-i486-1 sudo-1.6.8p12-i486-1 gcc-g++-3.4.6-i486-1 sysklogd-1.4.1-i486-9 glibc-2.3.6-i486-6 sysvinit-2.84-i486-69 glibc-solibs-2.3.6-i486-6 tar-1.15.1-i486-2 grep-2.5-i486-3 tcpip-0.17-i486-39 groff-1.19.2-i486-1 traceroute-1.4a12-i386-2 gzip-1.3.5-i486-1 util-linux-2.12r-i486-5 hdparm-6.6-i486-1 zlib-1.2.3-i486-1 |
Your choice may be different in parts, but it should give you some idea. After installation I went on to wipe cruft out of /etc and to simplify almost every aspect of system configuration. Simplification is particularly important since /etc is going to be mounted in a RAM disk in stage 3 of this guideline.
For those of you interested in how far you can go, here is an overview of the content of my /etc for your perusal.
root@conner:~# ls -R /etc /etc: HOSTNAME hosts login.access profile shadow adjtime inetd.conf login.defs proftpd.conf shadow- apache/ inittab modules.devfs protocols shells devfsd.conf inputrc motd random-seed squid/ dialogrc ioctl.save mtab rc.d/ sudoers fdprm issue networks resolv.conf syslog.conf fstab ld.so.cache nscd.conf screenrc termcap group ld.so.conf nsswitch.conf securetty hardwareclock lilo.conf@ passwd service/ host.conf localtime ppp/ services /etc/apache: access.conf httpd.conf magic mime.types srm.conf /etc/ppp: auth-down* chap-secrets ip-up* ipx-up* resolv.conf auth-fail* ip-down* ip-up.bind* options auth-up* ip-mark* ipx-down* pap-secrets /etc/rc.d: rc.0* rc.3* rc.crond* rc.httpd* rc.mrhttpd* rc.sap* rc.1* rc.6* rc.dnscache* rc.inet1* rc.overlay* rc.squid* rc.2* rc.S* rc.firewall* rc.inetd* rc.pppd* rc.syslog* /etc/service: dnscache/ ftp/ http/ ppp/ telnet/ /etc/service/dnscache: env/ log/ root/ run* seed /etc/service/dnscache/env: CACHESIZE DATALIMIT FORWARDONLY IP IPSEND ROOT /etc/service/dnscache/log: main/ run* status /etc/service/dnscache/log/main: /etc/service/dnscache/root: ip/ servers/ /etc/service/dnscache/root/ip: 127.0.0.1 192.168.157 /etc/service/dnscache/root/servers: \@ /etc/service/ftp: run* /etc/service/http: run* /etc/service/ppp: run* /etc/service/telnet: run* supervise/ /etc/service/telnet/supervise: control| lock ok| status /etc/squid: cachemgr.conf mime.conf squid.conf |
I also tend to install the latest 2.4 kernel from kernel.org. Note that the later 2.4 kernels require a glibc patch with older versions of Slackware. The easiest solution, of course, is to install the latest Slackware version.
Now, why 2.4 I hear you asking? I have to confess that I run 2.6 on all other machines. However, 2.4 is smaller, more mature, and provides devfs, as described in stage 3 of this guideline. Devfs has been removed from current 2.6 kernels where it is replaced by a different system called udev. There have been semi-religious discussions about the topic. I for my part will not entertain these discussions. I might move to 2.6 and udev on the router at some point, but for the time being my motto is "never change a running system".
So much about setting up the system. Make sure you also read the original article which contains some background information that is still valid.
As a first step the functional scope and every expectation should be specified. Truth be told, my requirements have changed over time, often with a rapid prototyping approach. Nonetheless I can list them as they are at the time of writing:
This list almost implies the installation of a ppp daemon, a dns resolver, a caching HTTP proxy, a telnet daemon, an ftp daemon and a web server, combined with a firewall script and some glue to make it all work.
Before talking about the installation and configuration of all those components, I would like to describe the new system of run levels, as specified in /etc/inittab and the corresponding scripts in /etc/rc.d. The router knows the following runlevels:
| Level | Description |
| S | Startup of system |
| 1 | Local maintenance mode (keyboard, VGA screen) |
| 2 | LAN maintenance mode (telnet, ftp, http) |
| 3 | Productive mode (ppp, dns, http proxy, NAT) |
| 0 | System halting |
| 6 | System rebooting |
The use of runlevel 2 is a deviation from standard Slackware definition. It is very useful since it allows low-level maintenance of the router system without having to connect a keyboard and a monitor to the wall-mounted unit.
Let's look at the script /etc/rc.d/rc.2 which is called by init when the system enters runlevel 2:
#!/bin/bash # # Martin Rogge, 18/08/2002, 16/03/2003, 26/04/2003, 10/08/2004, 06/02/2006 # # Tell the viewers what's going to happen. echo "Going from runlevel $PREVLEVEL to runlevel $RUNLEVEL..." if [ $PREVLEVEL '>' 9 ]; then PREVLEVEL=0 fi if [ $PREVLEVEL '<' 2 ]; then # Start the syslogd and klogd daemons . /etc/rc.d/rc.syslog start # Initialize the transport layer . /etc/rc.d/rc.inet1 # Start the crond server . /etc/rc.d/rc.crond start # Start the inetd server . /etc/rc.d/rc.inetd start # Start the web server . /etc/rc.d/rc.mrhttpd start fi if [ $PREVLEVEL '>' 2 ]; then # Stop the Squid proxy server . /etc/rc.d/rc.squid stop # Stop the DNS resolver . /etc/rc.d/rc.dnscache stop # Stop the PPP daemon . /etc/rc.d/rc.pppd stop fi # Set up firewall rules . /etc/rc.d/rc.firewall internal |
As you can see, depending on the previous run level, the script will stop or start services to reach the desired set of running services. The other scripts in /etc/rc.d/ work in the same way. All those scripts have in common that they only manage the services explicitly described here. Services unknown to the scripts are not affected.
Let's now look at the services in more detail.
Pppd is the ppp daemon (surprise, surprise). It manages dial-up connections using the PPP protocol. Note that most DSL connections are of this type, including mine. All you need is a pppd module that implements PPPoE (PPP over Ethernet) and a reference to it in the pppd configuration file /etc/ppp/options. After importing this module, pppd will accept the name of a network interface in place of a serial device in the configuration file, eg. eth1. The PPPoE module is part of the pppd distribution.
Development activities around pppd have not been very fast in recent years. You might have to google for a download location of version 2.4.4 since the official site seems to be a bit outdated, plus it seems to have an unreliable download section. It is then just a matter of compiling the software and installing it. The only thing to look out for is that "make install" might not install the PPPoE module rp-pppoe.so to where you want it. I had to copy it manually to /usr/local/lib/pppd/2.4.4/rp-pppoe.so.
For reference, my configuration file /etc/ppp/options looks like this:
# General PPPD configuration options plugin /usr/local/lib/pppd/2.4.4/rp-pppoe.so eth1 noipdefault noauth default-asyncmap defaultroute hide-password usepeerdns mtu 1492 mru 1492 noaccomp noccp nobsdcomp nodeflate nopcomp novj novjccomp user "your_username_here" lcp-echo-interval 20 lcp-echo-failure 3 demand persist 192.168.158.2:192.168.158.1 ipcp-accept-remote ipcp-accept-local ktune |
Also interesting in this context is the topic of MTU and MRU. MTU stands for maximum transmission unit, and similarly MRU stands for maximum receive unit. They define the maximum packet size used by the IP protocol and are usually set to 1500 for most network interface cards. However, the PPPoE protocol requires 8 bytes for itself, so for optimum performance (in order to avoid fragmentation), the rest of the local area network should set their MTU and MRU to 1492. This involves some clever tweaking, particularly with computers running MS Windows. Google is your friend.
The following is the script setting up my router's network interfaces during system startup:
#!/bin/bash
#
# rc.inet1 This shell script sets up the transport layer
#
# Martin Rogge, 18/08/2002, 16/03/2003, 07/01/2004, 22/01/2006
#
# Attach the loopback device
/sbin/ifconfig lo 127.0.0.1
/sbin/route add -net 127.0.0.0 netmask 255.0.0.0 dev lo
# echo "Attempting to configure eth0 by contacting a DHCP server..."
# /sbin/dhcpcd -t 10 -d eth0
echo "Configuring eth0 as 192.168.157.7..."
/sbin/ifconfig eth0 192.168.157.7 broadcast 192.168.157.255 netmask 255.255.255.0 up mtu 1492
if [ ! $? = 0 ]; then
echo "Failed to configure eth0 as 192.168.157.7..."
fi
echo "Configuring eth1 for PPPoE..."
/sbin/ifconfig eth1 up mtu 1500
if [ ! $? = 0 ]; then
echo "Failed to configure eth1 for PPPoE..."
fi
# Default gateway
# /sbin/route add default gw 192.168.157.7 metric 1 dev eth0
# EOF
|
If you have a contract with your ISP based on online time you will want pppd to close the connection after a certain timeout. However, you do not want to count unsolicited packets from the outside world (since they arrive all the time, be it from infected Windows machines, be it through P2P protocols). The official way of achieving this involves some crazy functionality named PPP packet filtering. The easier way is fixing the coding where the check is made. This is what open source is all about, is it not? Below I copied the patch for pppd version 2.4.1.
--- auth.c-original Mon Mar 12 23:54:33 2001
+++ auth.c Tue Mar 13 10:54:11 2001
@@ -761,7 +761,7 @@
if (idle_time_hook != 0) {
tlim = idle_time_hook(&idle);
} else {
- itime = MIN(idle.xmit_idle, idle.recv_idle);
+ itime = idle.xmit_idle;
tlim = idle_time_limit - itime;
}
if (tlim <= 0) {
|
Pppd also enables us to keep a log of online times as well as updating the DNS resolver with the current list of DNS servers published by the ISP at the time of going online. This can be achieved through the scripts /etc/ppp/ip-up and /etc/ppp/ip-down that are executed by pppd when a connection is established or closed, respectively. We will return to this subject after looking at the other services.
Djbdns is a surprisingly controversial package containing (amongst other binaries) a dns server called tinydns and a dns resolver called dnscache. The software itself is small, efficient, robust and secure, particularly when compared to some of the dinosaur packages occupying the ground since the early unix days. From my point of view, the controversy is to some extent about a new kid on the block performing better than the older kids, but also about the strong and uncompromising views of the creator - Daniel J. Bernstein.
From the attributes I mentioned it is clear that dnscache is the DNS resolver of choice for my router. Installation could be easy (download from http://cr.yp.to/djbdns/install.html, compile and install) if it wasn't for two things we must tackle independently.
One is the directory structure. The installation procedure expects the services to be run from an environment called tcp tools (also provided by Daniel J. Bernstein). This entails that they expect to be run in a specific directory structure under /service. Tcp tools exercise process control similar to a combination of init and inetd, an option which I would like to decline (especially since dnscache is so stable anyway). Therefore my startup script /service/dnscache/run has been changed to:
#!/bin/sh
#exec >/dev/vc/5
#exec >>/var/log/dnscache/dnscache.log
exec >/var/log/dnscache/dnscache.log
exec 2>&1
exec <seed
exec /usr/local/bin/envdir /service/dnscache/env \
sh -c 'exec /usr/local/bin/envuidgid dnscache /usr/local/bin/dnscache'
|
I do not feel strongly about the directory structure, but since I want the root file system to be read-only, I chose to make /service a symbolic link to /etc/service.
The second issue is more subtle. I mentioned earlier, the ISP provides us at the time of establishing a PPPoE connection with the IP addresses of the current DNS servers that can be used for forwarding. This information is passed to the script /etc/ppp/ip-up. Unfortunately, with the distributed version of dnscache the only way of making it re-read the DNS servers is to kill and restart it.
I have created a patch that makes dnscache re-read the list of forwarders when receiving a SIGHUP signal. As far as I can see this modification should be safe - from my understanding of when signal handlers are called there should be no race possible. However, I must clearly state that the modification is not endorsed by Daniel J. Bernstein and that all inquiries and error reports should go to ME only.
*** dnscache.b 2001-02-11 22:11:45.000000000 +0100
--- dnscache.c 2007-02-28 21:13:46.000000000 +0100
***************
*** 1,3 ****
--- 1,4 ----
+ #include <signal.h>
#include <unistd.h>
#include "env.h"
#include "exit.h"
***************
*** 386,391 ****
--- 387,398 ----
char seed[128];
+ void signal_handler(const int sig)
+ {
+ if (sig == SIGHUP)
+ roots_init();
+ }
+
int main()
{
char *x;
***************
*** 442,447 ****
--- 449,456 ----
if (socket_listen(tcp53,20) == -1)
strerr_die2sys(111,FATAL,"unable to listen on TCP socket: ");
+ signal(SIGHUP, signal_handler);
+
log_startup();
doit();
}
|
Squid is a caching http proxy. It cuts down response time for http clients by caching the requested resources locally on the proxy server (either in memory or on disk). Note this is probably not noticeable within a given user session because usually (depending on the user settings) the web browser itself caches the requested objects. However, across users or across sessions the effect should be noticeable.
Installation is simple. Download the latest stable package from http://www.squid-cache.org/, compile it and install it according to the documentation. The interesting part is the configuration. I can show you my configuration file, but your guess is as good as mine.
# General Settings http_port 8080 icp_port 0 cache_effective_user squid cache_effective_group squid visible_hostname conner.home # ACL definition acl all src 0.0.0.0/0.0.0.0 acl localhost src 127.0.0.0/8 acl to_localhost dst 127.0.0.0/8 acl ssl_ports port 443 acl proxy_ports port 80 acl proxy_ports port 443 acl connect method CONNECT acl home src 192.168.157.0/24 # Access rules http_access deny !proxy_ports http_access deny connect !ssl_ports http_access deny to_localhost http_access allow home http_access deny all http_reply_access allow all icp_access deny all # Files & directories cache_dir ufs /var/cache/squid 1000 16 256 cache_access_log /var/log/squid/access.log cache_log /var/log/squid/cache.log cache_store_log /var/log/squid/store.log pid_filename /var/log/squid/squid.pid coredump_dir /var/log/squid # Cache Settings cache_mem 384 MB maximum_object_size 100000 KB maximum_object_size_in_memory 100 KB # Other settings forwarded_for off auth_param basic children 5 auth_param basic realm Squid proxy-caching web server auth_param basic credentialsttl 12 hours refresh_pattern . 60 20% 10080 |
These are part of the Slackware distribution. The only adaptation from my side happens within the configuration files which I simply quote here.
# This is inetd.conf ftp stream tcp nowait root /usr/sbin/proftpd proftpd telnet stream tcp nowait root /usr/sbin/in.telnetd in.telnetd |
# ProFTPD configuration file. # # Martin Rogge, 30/07/2002, 22/01/2006 ServerName "ProFTPD" ServerType inetd DefaultServer on Port 21 Umask 022 MaxInstances 10 User ftp Group ftp RootLogin on UseReverseDNS off IdentLookups off PassivePorts 65000 65535 SystemLog /var/log/proftpd/proftpd.log TransferLog /var/log/proftpd/transfer.log <Directory /*> AllowOverwrite on </Directory> <Anonymous ~ftp> RequireValidShell off User ftp Group ftp UserAlias anonymous ftp </Anonymous> |
Some background information can be found in the original article.
The router provides web services strictly to the LAN - to report connection state, online time and data volume, and to allow clients to execute selected actions on the router.
In previous versions of the router I had used Apache web server for this purpose. Apache is probably the most widely used web server on this planet. It has enterprise software strength and runs a large part of the internet. I have never had any problem with Apache. It is, however, a big application that uses up resources. Another drawback: due to Apache relying on files in the /etc directory, it is not quite compatible with my runlevel system, because the idea is that in runlevel 2 (when the web server is still running) it is possible to unmount /etc for maintenance work. Remember, during normal startup the /etc directory is copied from the read-only root partition into a RAM disk and subsequently mounted under /etc.
When thinking about it rationally, I basically had the following requirements for a web server:
Since I could not find a suitable solution on the open source market, I decided to write a HTTP server especially for my router scenario. The result is called MrHTTPD, and it is available on Sourceforge.
MrHTTPD is a threaded design that matches all the requirements above. The binary is around 11 kilobytes in size, that is 1/30 of Apache httpd. Without HTTP Keepalive, it serves static files 3 times as fast as Apache. It has been volume tested and is now installed as the productive web server on my router.
Installation is straightforward: download the source from Sourceforge, unpack it and follow the instructions.
As you might know, NAT (network address translation) is called IP masquerading in the Linux world. IP masquerading and firewalling are fundamental kernel functions, controlled by the user space tool iptables. In the original article I have explained the functioning and the setup of IP masquerading and firewalling in great detail.
During startup, apart from the kernel daemons, only devfsd is started. On runlevel 1, syslogd, klogd and crond are started. On runlevel 2, inetd and mrhttpd are started. Eventually, on runlevel 3, the productive services pppd, dnscache and squid are started, and the NAT function is switched on.
The complete process list of the router operating in runlevel 3 looks like this:
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD 4 S 0 1 0 2 69 0 - 157 1438dd ? 00:00:07 init 1 S 0 2 1 0 69 0 - 0 12024d ? 00:00:00 keventd 1 S 0 3 1 0 79 19 - 0 118095 ? 00:00:00 ksoftirqd_CPU0 1 S 0 4 1 0 69 0 - 0 12c6f7 ? 00:00:00 kswapd 1 S 0 5 1 0 69 0 - 0 138802 ? 00:00:00 bdflush 1 S 0 6 1 0 69 0 - 0 1388da ? 00:00:00 kupdated 1 S 0 7 1 0 69 0 - 0 16b731 ? 00:00:00 kjournald 1 S 0 11 1 0 69 0 - 377 17c16d ? 00:00:00 devfsd 1 S 0 21 1 0 69 0 - 0 16b731 ? 00:00:00 kjournald 1 S 0 32 1 0 69 0 - 380 1438dd ? 00:00:00 syslogd 5 S 0 34 1 0 69 0 - 368 1152bf ? 00:00:00 klogd 1 S 0 40 1 0 69 0 - 418 11bcb7 ? 00:00:00 crond 5 S 0 42 1 0 72 0 - 376 1438dd ? 00:00:00 inetd 5 S 16 44 1 0 69 0 - 501 1438dd ? 00:00:00 mrhttpd 5 S 0 46 1 0 69 0 - 543 1438dd ? 00:00:00 pppd 4 S 17 47 1 0 69 0 - 933 1440cc ? 00:00:00 dnscache 1 S 0 49 1 0 69 0 - 957 116bce ? 00:00:00 squid 4 S 19 52 49 0 74 0 - 2310 1440cc ? 00:00:00 squid 0 S 19 74 52 0 69 0 - 329 13d173 ? 00:00:00 unlinkd 0 S 0 154 1 0 69 0 - 366 188797 vc/1 00:00:00 agetty 0 S 0 155 1 0 69 0 - 366 188797 vc/2 00:00:00 agetty 0 R 0 160 42 0 73 0 - 399 - ? 00:00:00 in.telnetd 4 S 0 161 160 0 71 0 - 593 116bce pts/0 00:00:00 bash 0 R 0 166 161 0 71 0 - 477 - pts/0 00:00:00 ps |
Yeah, alright. In the original article I promised to move over to SSH (secure shell) as a replacement of telnet and ftp. So far, lazyness and "never change a running system" have prevented it. But one fine day...
SMB is the proprietary network protocol used by Microsoft Windows. Although there is very good server and client support in the open source world, I do not see a reason for it in my application. On the contrary, for security reasons I make sure that any IP packet related to SMB is filtered out by the firewall, no matter what interface it came from.
This section is about topics that affect the setup of more than one service. Let us first turn towards DNS forwarding. Essentially we have set up dnscache to forward all DNS queries to the provider's DNS servers. Dnscache reads them from the file /service/dnscache/root/servers/@. In this document I have also described how to patch dnscache to re-read that file after receiving a SIGHUP signal.
The only thing remaining to be done is to update /service/dnscache/root/servers/@ and send dnscache the SIGHUP signal whenever the DNS servers change. This typically happens when a PPP connection is established. For that reason we add coding in the script /etc/ppp/ip-up that updates the file and sends the signal if required. For illustration purposes I shall print only the part of the script that deals with updating the DNS setting. In case you were wondering, I use Perl as script language for /etc/ip-up.
# Check if IP addresses are well formed
if (($ENV{'DNS1'} !~ /(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/) ||
($ENV{'DNS2'} !~ /(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/)) {
exit 0;
}
open(DNS,'</service/dnscache/root/servers/@');
@output = <DNS>;
close DNS;
my $OLD1 = $output[0]; chop $OLD1;
my $OLD2 = $output[1]; chop $OLD2;
# Check if DNS servers have actually changed
if ( ($ENV{'DNS1'}, $ENV{'DNS1'}) eq ($OLD1, $OLD2) ) {
exit 0;
}
open(DNS,'>/service/dnscache/root/servers/@');
print DNS <<EOF;
$ENV{'DNS1'}
$ENV{'DNS2'}
EOF
close DNS;
# The following requires a modified version of dnscache!
system('killall -1 dnscache 2> /dev/null');
|
Note that some people have suggested to empty the file /service/dnscache/root/servers/@ when the PPP connection goes down. This could be done in the script /etc/ppp/ip-down. It has the effect that dnscache answers DNS queries without delay when offline. The answer, of course, is "service not available". This makes sense in a scenario where you establish the PPP connection manually. In my case it makes more sense to keep the file when offline, because dnscache will then forward a new query to one of those servers and in doing so triggers PPP to establish a new connection to the ISP.
Let us now discuss the tracking and reporting of online time and data volume. This is certainly interesting information for the person in the household doing the system management, the bills, etc. In other words: me. It becomes particularly important when you are not on a flat rate, but even on a flat rate it might not be a bad idea to log these fundamentals in case you ever get into an argument with your ISP.
In order to do meaningful reporting, first we need to collect suitable data. I have chosen the following mechanism. The system records certain events in a set of log files. The log files are created chronologically according to the naming scheme iplog.<year>.<month> so that each event is stored in the file corresponding to the month and year of its time stamp. The following events are recorded:
| Event | Trigger | Script | Data |
| Going online | pppd | /etc/ppp/ip-up | Time stamp, Interfaces, IP addresses, Data volume, DNS servers |
| Going offline | pppd | /etc/ppp/ip-down | Time stamp, Interfaces, IP addresses, Data volume |
| Midnight | crond | /etc/ppp/ip-mark | Time stamp, Data volume |
To give you a better impression, this is a typical excerpt of a log file:
15.02.2007 00:00:01 ip-mark RX:365018302 TX:34226294 15.02.2007 01:40:43 ip-down 0:ppp0 1:eth1 2:0 3:217.233.178.139 4:217.0.116.218 RX:368348239 TX:34731083 15.02.2007 01:44:04 ip-up 0:ppp0 1:eth1 2:0 3:217.233.175.159 4:217.0.116.218 RX:368348791 TX:34731768 DNS1:217.237.151.142 DNS2:217.237.151.51 15.02.2007 01:52:04 ip-down 0:ppp0 1:eth1 2:0 3:217.233.175.159 4:217.0.116.218 RX:368354245 TX:34740481 15.02.2007 01:53:58 ip-up 0:ppp0 1:eth1 2:0 3:217.233.164.1 4:217.0.116.218 RX:368354797 TX:34741070 DNS1:217.237.151.142 DNS2:217.237.151.51 15.02.2007 02:39:00 ip-down 0:ppp0 1:eth1 2:0 3:217.233.164.1 4:217.0.116.218 RX:369048676 TX:34848273 15.02.2007 08:16:00 ip-up 0:ppp0 1:eth1 2:0 3:217.233.167.55 4:217.0.116.218 RX:369049288 TX:34848744 DNS1:217.237.151.142 DNS2:217.237.151.51 16.02.2007 00:00:01 ip-mark RX:419479288 TX:40691543 |
Let's now review the three scripts. First comes /etc/ppp/ip-up. Please observe that I have snipped the coding to update the DNS servers since it is already listed above. Connoisseurs will notice that the script is written in Perl, a language very much suited to string operations.
#!/usr/bin/perl
#
# ip-up
#
# log event and update DNS servers
#
# Martin Rogge, 25/12/2003, 17/01/2004, 14/08/2004, 01/03/2007
my ($sec,$min,$hour,$day,$month,$year) = (localtime)[0,1,2,3,4,5];
$month += 1;
$year += 1900;
my $date = sprintf("%02s.%02s.%04s %02s:%02s:%02s", $day, $month, $year, $hour, $min, $sec);
my $filename = sprintf("/var/iplog/ip.log.%04s%02s", $year, $month);
open(OUT,">>$filename");
print OUT "$date ip-up ";
my $i = 0;
foreach (@ARGV) {
print OUT " $i:$_";
$i++;
}
my @output = `/sbin/ifconfig eth1`;
foreach (@output) {
if (/RX bytes:(\d+)/) {
print OUT " RX:", $1;
}
if (/TX bytes:(\d+)/) {
print OUT " TX:", $1;
}
}
print OUT " DNS1:", $ENV{'DNS1'};
print OUT " DNS2:", $ENV{'DNS2'};
print OUT "\n";
close OUT;
open(OUT,">/var/iplog/ip.status");
print OUT "online\n";
close OUT;
# update of DNS settings follows here...
|
The corresponding code in /etc/ppp/ip-down:
#!/usr/bin/perl
#
# ip-down
#
# log event
#
# Martin Rogge, 25/12/2003, 17/01/2004, 14/08/2004
my ($sec,$min,$hour,$day,$month,$year) = (localtime)[0,1,2,3,4,5];
$month += 1;
$year += 1900;
my $date = sprintf("%02s.%02s.%04s %02s:%02s:%02s", $day, $month, $year, $hour, $min, $sec);
my $filename = sprintf("/var/iplog/ip.log.%04s%02s", $year, $month);
open(OUT,">>$filename");
print OUT "$date ip-down";
my $i = 0;
foreach (@ARGV) {
print OUT " $i:$_";
$i++;
}
my @output = `/sbin/ifconfig eth1`;
foreach (@output) {
if (/RX bytes:(\d+)/) {
print OUT " RX:", $1;
}
if (/TX bytes:(\d+)/) {
print OUT " TX:", $1;
}
}
print OUT "\n";
close OUT;
open(OUT,">/var/iplog/ip.status");
print OUT "offline\n";
close OUT;
|
Finally /var/spool/cron/crontabs/root:
0 0 * * * /etc/ppp/ip-mark 1>/dev/null |
This entry causes crond to execute the script /etc/ppp/ip-mark every midnight:
#!/usr/bin/perl
#
# ip-mark
#
# put a mark into the ip log
#
# Martin Rogge, 25/12/2003, 17/01/2004, 14/08/2004, 18/02/2006
my ($sec,$min,$hour,$day,$month,$year) = (localtime)[0,1,2,3,4,5];
$month += 1;
$year += 1900;
my $date = sprintf("%02s.%02s.%04s %02s:%02s:%02s", $day, $month, $year, $hour, $min, $sec);
my $filename = sprintf("/var/iplog/ip.log.%04s%02s", $year, $month);
open(OUT,">>$filename");
print OUT "$date ip-mark";
my @output = `/sbin/ifconfig eth1`;
foreach (@output) {
if (/RX bytes:(\d+)/) {
print OUT " RX:", $1;
}
if (/TX bytes:(\d+)/) {
print OUT " TX:", $1;
}
}
print OUT "\n";
close OUT;
|
Recording the events is one thing. Compressing them into meaningful information is another. For that purpose I have created a series of more or less elaborate CGI scripts that are way to long to be included in this text. Instead, I give you a link to the output of a typical report. The output is based on the log file quoted above.
My method of making the root file system read-only has not changed much from the method described in the original article. It involves identifying exactly which files should have write access during system operation, and placing them on a suitable filesystem that is made writeable during system startup. At present I have the following filesystems in operation:
root@conner:~# cat /proc/mounts rootfs / rootfs rw 0 0 /dev/root / ext3 ro 0 0 none /dev devfs rw 0 0 ramfs /etc ramfs rw 0 0 ramfs /tmp ramfs rw 0 0 proc /proc proc rw 0 0 /dev/hda2 /var ext3 rw 0 0 |
As you can see, the root file system is mounted read-only. Another hard disk partition is mounted read-write under /var. This is the place for log files, and in particular, for the http cache. The /dev directory is taken care of by the devfs file system. The /tmp directory is a ram disk that is initially empty. And finally, the /etc directory is mounted as ramdisk that is initially filled with the content of /etc from the root file system.
An interesting option to be explored in future is the concept of a union file system. A union file system can implement a copy-on-write mechanism, ie. it can merge a read-only file system with a ram disk transparently, so that files opened for writing are copied into the ramdisk, from where they overlay the corresponding file in the read-only file system. An example of that can be seen when looking at Knoppix, a Linux distro that runs entirely from CD-ROM or DVD.
In the original article I described in detail how the root file system is transferred onto a CD-ROM. However, at that time the aim was a completely diskless operation. Since I now provide http caching, there has to be a hard disk in the system, and we might as well run the root file system directly from the hard disk. I still make it read-only for added security. This may be less secure than running it from CD-ROM, but it would require root access to do any lasting changes.
Hope you found this article interesting. Feel free to send me an email if you have any questions or comments.
Martin
| < Original article |
|
Home > |