Subject: poor man's demand-dial
To: None <current-users@NetBSD.ORG>
From: Roland McGrath <roland@frob.com>
List: current-users
Date: 03/20/1995 04:05:29
After pondering for a while how I might hack if_ppp.c and pppd to do
some sort of demand-dialing, I came up with a cheap solution that
doesn't require kernel changes, and almost works.

The tunnel driver provides an easy mechanism for any program at all to
come alive when someone tries to send a packet.

After putting "pseudo-device tun 2" in my kernel configuration and
recompiling my kernel (and doing "MAKEDEV tun0 tun1"), and with my PPP
not connected, I do:

ifconfig tun0 up `hostname` ppp-peer-address
route add default ppp-peer-address

using the IP address of the machine my PPP connection is with.

Now packets sent to ppp-peer-address, or anywhere else there is no
other route to, arrive as input on /dev/tun0.  So, I did:

(dd if=/dev/tun0 of=/dev/null bs=20 count=1
route delete default
sh /etc/ppp-connect) &

When I send a packet to the net, dd reads those 20 bytes of it (and
tun discards the remainder of the packet) and exits; when dd exits and
thus the last opener closes /dev/tun0, the driver automagically
configures the interface down and removes its routing table entry.
The subshell continues, and runs my ppp-connect script which sets up
the real net connection.

Voila, dialing on demand.  One probably wants the connection dialed
and opened only for real traffic, not random pings; so I quickly
expanded it to:

(dd if=/dev/tun0 conv=unblock ibs=20 obs=21 cbs=20 | 
sed -n '/^.........[^]........../q'
route delete default
sh /etc/ppp-connect) &

(If your mailer didn't like that literal ^A, in C syntax it said "[^\001]".)

This works quite well, but I foresaw writing regexps to match headers
containing various specified port and address numbers getting old fast.
So I found pcap(3) and wrote the following program, which I use like this:

(tunfilter </dev/tun0 not icmp and not dst port ntp
route delete default
sh /etc/ppp-connect) &


This is all clearly far from wondrously pleasant to configure and use
(though the packet filter in sed was its own reward).  Hopefully
someone will write a nice program which uses this basic functionality
to provide seamless demand-dialing bliss.

The remaining obstacle to such a thing is that pppd does not cope well
with being hung up on.  From reading the code in the kernel and pppd,
I cannot see why it fails; the loss of carrier detect should produce a
SIGHUP sent to pppd, which should die gracefully on receipt of it.
But in actuality, when the modem disconnects, pppd hangs there unaware
with the line still in PPP line discipline and prevents anything else
from using the line.


#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <net/if.h>
#include <netinet/if_ether.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <pcap.h>

int
main (int argc, char **argv)
{
  char *copy_argv();
  char *cmdbuf = copy_argv (&argv[1]);
  int pcapbuf[500];
  struct bpf_program filter;
  unsigned char inbuf[IP_MAXPACKET];
  int nread;

  bzero (pcapbuf, sizeof pcapbuf);
  pcapbuf[1] = 1;
  if (pcap_compile ((pcap_t*)pcapbuf, &filter, cmdbuf, 1, 0UL))
    {
      pcap_perror ((pcap_t*)pcapbuf, "pcap_compile");
      return 1;
    }

  free (cmdbuf);

  do
    {
      nread = read (0, inbuf+1, sizeof inbuf-1);
      if (nread < 0)
	{
	  perror ("read");
	  return 1;
	}
      else if (nread == 0)
	{
	  fprintf (stderr, "read eof\n");
	  return 1;
	}
      *(u_short *) inbuf = htons (ETHERTYPE_IP);
    } while (bpf_filter (filter.bf_insns, inbuf+1, nread, nread) <= 0);

  return 0;
}

/*
 * Copy arg vector into a new buffer, concatenating arguments with spaces.
 */
char *
copy_argv(register char **argv)
{
	register char **p;
	register int len = 0;
	char *buf;
	char *src, *dst;

	p = argv;
	if (*p == 0)
		return 0;

	while (*p)
		len += strlen(*p++) + 1;

	buf = malloc(len);

	p = argv;
	dst = buf;
	while ((src = *p++) != NULL) {
		while ((*dst++ = *src++) != '\0')
			;
		dst[-1] = ' ';
	}
	dst[-1] = '\0';

	return buf;
}