Subject: Watching interface address changes
To: None <tech-userlevel@NetBSD.ORG>
From: Martin Husemann <martin@duskware.de>
List: tech-userlevel
Date: 09/02/2001 20:35:42
As some of you might know I've been doing PPPoE support and added the kernel
bits (and the minimal userland configuration tool) to -current some months
ago. This has not yet been documented or announced, as it was lacking userland
integration.

The main obstacle on the way to complete this has been the lack of an ability
to execute scripts at IPCP up/down events (see pppd's /etc/ppp/ip-up and
/etc/ppp/ip-down scripts).

I would have prefered a more general "execute a configurable script on 
arbitrary kernel events" mechanism, that could be reused for APM, pcmcia
card insertions/removals and what have you. One easy way to implement such
a daemon would be to use the kevent stuff (currently on a branch) and define
a coherent namespace for all kernel events that might be interesting to
userland. The "keventd" could then read a configuration file, do a "knote"
on every event that has a script specified, wait for any event and then run
the script associated with that event.

Obviously this will not happen soon, as kevent first has to move into the
mainline and we must conclude on the coherent namespace for kernel events.

So I picked up plan B and implemented a minimal, maybe a little bit too 
specific utility, that does what I need with minimal effort. The source is
attached below - it's a simple daemon listening on the routing socket and
running scripts at address deletion or addition events (for the specified
interface).

I'm open to other sugestions, comments, anything.

I'd like to import this utility as /usr/sbin/ifwatchd and then proceed to
finish PPPoE support and documentation.


Martin

# This is a shell archive.  Save it in a file, remove anything before
# this line, and then unpack it by entering "sh file".  Note, it may
# create directories; files and directories will be owned by you and
# have default permissions.
#
# This archive contains:
#
#	ifwatchd.c
#	ifwatchd.8
#	Makefile
#
echo x - ifwatchd.c
sed 's/^X//' >ifwatchd.c << 'END-of-ifwatchd.c'
X/*
X * Copyright (c) 2001 Martin Husemann <martin@duskware.de>
X * All rights reserved.
X *
X * Redistribution and use in source and binary forms, with or without
X * modification, are permitted provided that the following conditions
X * are met:
X * 1. Redistributions of source code must retain the above copyright
X *    notice, this list of conditions and the following disclaimer.
X * 2. The name of the author may not be used to endorse or promote products
X *    derived from this software withough specific prior written permission
X *
X * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
X * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
X * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
X * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
X * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
X * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
X * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
X * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
X * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
X * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
X */
X
X
X#include <sys/types.h>
X#include <sys/param.h>
X#include <sys/file.h>
X#include <sys/socket.h>
X#include <sys/ioctl.h>
X#include <sys/mbuf.h>
X#include <sys/sysctl.h>
X
X#include <net/if.h>
X#include <net/route.h>
X
X#include <stdio.h>
X#include <stdlib.h>
X#include <string.h>
X#include <sysexits.h>
X#include <unistd.h>
X
X#include <netinet/in.h>
X#include <arpa/inet.h>
X#include <netdb.h>
X
X/* local functions */
Xstatic void useage(void);
Xstatic void dispatch(void*, size_t);
Xstatic void check_addrs(char *cp, int addrs, int is_up);
Xstatic void invoke_script(struct sockaddr *sa, int is_up);
X
X#define	IFA_BIT		(1<<5)	/* this bit is set for interface addresses */
X
X/* stolen from /sbin/route */
X#define ROUNDUP(a) \
X	((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))
X#define ADVANCE(x, n) (x += ROUNDUP((n)->sa_len))
X
X/* global variables */
Xstatic int if_index = -1;
Xstatic int verbose = 0;
Xstatic const char *up_script = NULL;
Xstatic const char *down_script = NULL;
X
Xint
Xmain(int argc, char **argv)
X{
X	int c, s, n;
X	int errs = 0;
X	char msg[2048], *msgp;
X
X	while ((c = getopt(argc, argv, "vhu:d:")) != -1)
X		switch (c) {
X		case 'h':
X			useage();
X			return 0;
X		case 'v':
X			verbose++;
X			break;
X
X		case 'u':
X			up_script = optarg;
X			break;
X
X		case 'd':
X			down_script = optarg;
X			break;
X
X		default:
X			errs++;
X			break;
X		}
X
X	if (errs)
X		useage();
X
X	argv += optind;
X	argc -= optind;
X
X	if (argc <= 0) 
X		useage();
X
X	if_index = if_nametoindex(argv[0]);
X	if (if_index == 0) {
X		fprintf(stderr, "%s: not a network interface\n", argv[0]);
X		exit(1);
X	}
X
X	if (verbose) {
X		printf("up_script: %s\ndown_script: %s\ninterface: #%d\n",
X			up_script, down_script, if_index);
X		printf("verbosity = %d\n", verbose);
X	}
X
X	s = socket(PF_ROUTE, SOCK_RAW, 0);
X	if (s < 0) {
X		perror("open routing socket");
X		exit(1);
X	}
X
X	for (;;) {
X		n = read(s, msg, sizeof msg);
X		msgp = msg;
X		for (msgp = msg; n > 0; n -= ((struct rt_msghdr*)msgp)->rtm_msglen, msgp += ((struct rt_msghdr*)msgp)->rtm_msglen) {
X			dispatch(msgp, n);
X			
X		}
X	}
X
X	close(s);
X
X	exit(0);
X}
X
Xstatic void
Xuseage()
X{
X	fprintf(stderr, 
X	    "useage:\n"
X	    "\tifwatchd [-h] [-v] [-u up-script] [-d down-script] ifname\n"
X	    "\twhere:\n"
X	    "\t -h       show this help message\n"
X	    "\t -u <cmd> specify command to run on interface up event\n"
X	    "\t -d <cmd> specify command to run on interface down event\n");
X	exit(1);
X}
X
Xstatic void
Xdispatch(void *msg, size_t len)
X{
X	struct rt_msghdr *hd = msg;
X	struct ifa_msghdr *ifam;
X	int is_up;
X
X	is_up = 0;
X	switch (hd->rtm_type) {
X	case RTM_NEWADDR:
X		if (hd->rtm_addrs != if_index) {
X			if (verbose > 1)
X				printf("ignoring new address for interface #%d\n", 
X					hd->rtm_addrs);
X		}
X		if (verbose)
X			printf("new address: interface %d\n", hd->rtm_addrs);
X		is_up = 1;
X		goto work;
X	case RTM_DELADDR:
X		if (hd->rtm_addrs != if_index) {
X			if (verbose > 1)
X				printf("ignoring delted address for interface #%d\n", 
X					hd->rtm_addrs);
X		}
X		if (verbose)
X			printf("delete address: interface %d\n", hd->rtm_addrs);
X		is_up = 0;
X		goto work;
X	}
X	if (verbose)
X		printf("unknown message ignored\n");
X	return;
X
Xwork:
X	ifam = (struct ifa_msghdr *)msg;
X	check_addrs((char *)(ifam + 1), ifam->ifam_addrs, is_up);
X}
X
Xstatic void
Xcheck_addrs(cp, addrs, is_up)
X	char    *cp;
X	int     addrs, is_up;
X{
X	struct sockaddr *sa;
X	int i;
X
X	if (addrs == 0)
X		return;
X	for (i = 1; i; i <<= 1)
X	    if (i & addrs) {
X		sa = (struct sockaddr *)cp;
X		if (i == IFA_BIT)
X			invoke_script(sa, is_up);
X		ADVANCE(cp, sa);
X	    }
X}
X
Xstatic void
Xinvoke_script(sa, is_up)
X	struct sockaddr *sa;
X	int is_up;
X{
X	char addr[NI_MAXHOST], *cmd;
X	const char *script;
X
X	if (sa->sa_len == 0) {
X	    fprintf(stderr, "illegal socket address (sa_len == 0)\n");
X	    return;
X	}
X
X	if (getnameinfo(sa, sa->sa_len, addr, sizeof addr, NULL, 0, NI_NUMERICHOST))
X	    return;	/* this address can not be handled */
X
X	script = is_up? up_script : down_script;
X	if (script == NULL) return;
X
X	asprintf(&cmd, "%s \"%s\"", script, addr);
X	if (cmd == NULL) {
X	    fprintf(stderr, "out of memory\n");
X	    return;
X	}
X	if (verbose)
X	    printf("calling: %s\n", cmd);
X	system(cmd);
X	free(cmd);
X}
END-of-ifwatchd.c
echo x - ifwatchd.8
sed 's/^X//' >ifwatchd.8 << 'END-of-ifwatchd.8'
X.\" Copyright (C) 2001 by Martin Husemann
X.\" All rights reserved.
X.\"
X.\" Redistribution and use in source and binary forms, with or without
X.\" modification, are permitted provided that the following conditions
X.\" are met:
X.\" 1. Redistributions of source code must retain the above copyright
X.\"    notice, this list of conditions and the following disclaimer.
X.\" 2. Redistributions in binary form must reproduce the above copyright
X.\"    notice, this list of conditions and the following disclaimer in the
X.\"    documentation and/or other materials provided with the distribution.
X.\"
X.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS
X.\" OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
X.\" WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
X.\" DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT,
X.\" INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
X.\" (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
X.\" SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
X.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
X.\" STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
X.\" IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
X.\" POSSIBILITY OF SUCH DAMAGE.
X.\"
X.Dd September 2, 2001
X.Os
X.Dt IFWATCHD 8
X.Sh NAME
X.Nm ifwatchd
X.Nd watch for adresses added to or deleted from interfaces and call up/down scripts for them
X.Sh SYNOPSIS
X.Nm
X.Op Fl h
X.Op Fl v
X.Op Fl d Ar down-script
X.Op Fl u Ar up-script
X.Ar ifname
X.Pp
X.Sh DESCRIPTION
X.Nm Ifwatchd
Xis used to monitor dynamic interfaces (for example PPP interfaces) for address
Xchanges. Sometimes this interfaces are acompanied by a daemon program, which
Xcan take care of running any necesary scripts (like pppd or isdnd), but 
Xsometimes the interfaces run completely autonomus (like pppoe).
X.Pp
X.Nm Ifwatchd
Xprovides a generic way to watch this type of changes. It works by monitoring
Xthe routing socket and interpreting
X.Ql RTM_NEWADDR
X.Pq address added
Xand
X.Ql RTM_DELADDR
X.Pq address deleted
Xmessages. It does not need special privileges to do this. The scripts called
Xfor up or down events are run with the same user id as
X.Nm
Xis run.
X.Pp
XThe following options are available:
X.Bl -tag -width indent
X.It Fl h
XShow the synopsis.
X.It Fl v
XOutput verbose progress messages and flag errors ignored during normal
Xoperation.
X.It Fl u Ar up-script
XSpecify the command to invoke on
X.Dq interface up
Xevents (or: addition of an address to an interface).
X.It Fl d Ar down-script
XSpecify the command to invoke on
X.Dq interface down
Xevents (or: deletion of an address from an interface).
X.It ifname
XThe name of the interface to watch. Events for other interfaces are ignored.
X.El
X.Pp
XNote: you can run
X.Nm
Xfrom 
X.Pa /etc/ifconfig.*
Xfiles via the
X.Em !
Xshell command syntax.
X.Sh EXAMPLES
X.Bd -literal
X# ifwatchd -u /etc/ppp/ip-up -d /etc/ppp/ip-down pppoe0
X.Ed
X.Sh SEE ALSO
X.Xr pppoe 8 ,
X.Xr ifconfig.if 5 ,
X.Xr rc.d 8 ,
X.Xr route 8 ,
X.Xr route 4
X.Sh HISTORY
XThe 
X.Nm
Xutility appeared in
X.Nx 1.6
X.Sh AUTHOR
XThe program was written by
X.ie t Martin Husemann.
X.el Martin Husemann.
END-of-ifwatchd.8
echo x - Makefile
sed 's/^X//' >Makefile << 'END-of-Makefile'
X#	$NetBSD$
X
XPROG=ifwatchd
XMAN=ifwatchd.8
X
X.include <bsd.prog.mk>
END-of-Makefile
exit