Subject: bin/23191: cable plug-in detection via ifwatchd
To: None <gnats-bugs@gnats.netbsd.org>
From: Brian Grayson <bgrayson@cs24279-4.austin.rr.com>
List: netbsd-bugs
Date: 10/18/2003 23:41:17
>Number:         23191
>Category:       bin
>Synopsis:       Enhancements to allow it to detect carrier status changes
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    bin-bug-people
>State:          open
>Class:          change-request
>Submitter-Id:   net
>Arrival-Date:   Sun Oct 19 04:42:01 UTC 2003
>Closed-Date:
>Last-Modified:
>Originator:     Brian Grayson
>Release:        NetBSD 1.6ZC
>Organization:
>Environment:
System: NetBSD cs24279-4.austin.rr.com 1.6ZC NetBSD 1.6ZC (K98) #1: Fri Oct 3 23:45:59 CDT 2003 bgrayson@cs24279-4.austin.rr.com:/sys/arch/i386/compile/K98 i386
Architecture: i386
Machine: i386
>Description:
  For laptop use, it would be convenient if ifwatchd would detect carrier
  status changes (that is, when an Ethernet cable is plugged in).  The
  included patch adds two new options, to support carrier-detected and
  carrier-lost events on the routing socket.

  The patch also adds/fixes a few other things:
  - The man page did not order the flag and argument descriptions properly.
  - Multiple -v's now make ifwatchd more verbose.
  - With two -v's, it prints out each routing socket message, allowing it to
    work as a routing-socket-tracer.

  If I tweak /sbin/dhclient-script to not do the ifconfig to 0.0.0.0 in the
  PREINIT section, then this patch to ifwatchd makes my ex0-based laptop
  do the right thing when I connect a cable.
>How-To-Repeat:
  Be frustrated by how NetBSD doesn't do auto-dhclient when you plug in a
  cable.
>Fix:
Index: ifwatchd.8
===================================================================
RCS file: /cvsroot/src/usr.sbin/ifwatchd/ifwatchd.8,v
retrieving revision 1.19
diff -5 -u -p -t -r1.19 ifwatchd.8
--- ifwatchd.8	2003/07/04 12:48:30	1.19
+++ ifwatchd.8	2003/10/19 04:33:16
@@ -45,31 +45,36 @@
 .Op Fl hiqv
 .Op Fl A Ar arrival-script
 .Op Fl D Ar departure-script
 .Op Fl d Ar down-script
 .Op Fl u Ar up-script
+.Op Fl c Ar carrier-script
+.Op Fl n Ar no-carrier-script
 .Ar ifname(s)
 .Sh DESCRIPTION
 .Nm
 is used to monitor dynamic interfaces (for example PPP interfaces)
-for address changes.
+for address changes, and to monitor static interfaces for carrier
+changes.
 Sometimes these interfaces are accompanied by a daemon program,
 which can take care of running any necessary scripts (like
 .Xr pppd 8
 or
 .Xr isdnd 8 ) ,
 but sometimes the interfaces run completely autonomously (like
 .Xr pppoe 4 ) .
 .Pp
 .Nm
-provides a generic way to watch this type of changes.
+provides a generic way to watch these types of changes.
 It works by monitoring the routing socket and interpreting
 .Ql RTM_NEWADDR
-.Pq address added
-and
+.Pq address added ,
 .Ql RTM_DELADDR
 .Pq address deleted
+and
+.Ql RTM_IFINFO
+.Pq carrier detect or loss of carrier
 messages.
 It does not need special privileges to do this.
 The scripts called for up or down events are run with the same user
 id as
 .Nm
@@ -81,10 +86,13 @@ The following options are available:
 Specify the command to invoke on arrival of new interfaces (like
 PCMCIA cards).
 .It Fl D Ar departure-script
 Specify the command to invoke when an interface departs (for example
 a PCMCIA card is removed.)
+.It Fl c Ar carrier-script
+Specify the command to invoke when the carrier status transitions from
+no carrier to carrier.
 .It Fl d Ar down-script
 Specify the command to invoke on
 .Dq interface down
 events (or: deletion of an address from an interface).
 .It Fl h
@@ -103,16 +111,15 @@ some of the monitored interfaces may alr
 finally starts, but their up-scripts have not been called.
 By default
 .Nm
 calls them on startup to account for this (and make the scripts
 easier.)
+.It Fl n Ar no-carrier-script
+Specify the command to invoke when the carrier status transitions from
+carrier to no carrier.
 .It Fl q
 Be quiet and don't log non-error messages to syslog.
-.It Ar ifname(s)
-The name of the interface to watch.
-Multiple interfaces may be specified.
-Events for other interfaces are ignored.
 .It Fl u Ar up-script
 Specify the command to invoke on
 .Dq interface up
 events (or: addition of an address to an interface).
 .It Fl v
@@ -123,10 +130,14 @@ normal operation.
 Adding more
 .Fl v
 increases the verbosity.
 .Em You do not want to use this option in
 .Pa /etc/rc.conf !
+.It Ar ifname(s)
+The name of the interface to watch.
+Multiple interfaces may be specified.
+Events for other interfaces are ignored.
 .El
 .Sh EXAMPLES
 .Bd -literal -offset indent
 # ifwatchd -u /etc/ppp/ip-up -d /etc/ppp/ip-down pppoe0
 .Ed
@@ -157,10 +168,38 @@ Use
 .Ed
 .Pp
 in your
 .Pa /etc/ifconfig.pppoe0
 file in the on-demand case.
+.Pp
+The next example is for dhclient users.
+.Bd -literal -offset indent
+# ifwatchd -i -c /etc/dhcp/carrier-detect tlp0
+.Ed
+.Pp
+With the above command, the carrier-detect script will be invoked when
+a carrier is detected on the interface 
+.Ar tlp0 .
+Note that the
+.Ar -i 
+flag prevents any action based on the initial state.
+A script like the following should work for most users, although it
+will not work for machines with multiple interfaces running 
+.Cm dhclient .
+.Bd -literal -offset indent
+#! /bin/sh
+# Arguments:  ifname tty speed address destination
+# If there is a dhclient already running, kill it.
+# (This step could be put in a distinct no-carrier script, if desired.)
+if [ -f /var/run/dhclient.pid ]; then
+       /bin/kill `/bin/cat /var/run/dhclient.pid`
+fi
+# Start dhclient again on this interface
+/sbin/dhclient $1
+.Ed
+
+
 .Sh PARAMETERS PASSED TO SCRIPTS
 The invoked scripts get passed these parameters:
 .Bl -tag -width destination
 .It Ar ifname
 The name of the interface this change is for (this allows to share
Index: ifwatchd.c
===================================================================
RCS file: /cvsroot/src/usr.sbin/ifwatchd/ifwatchd.c,v
retrieving revision 1.16
diff -5 -u -p -t -r1.16 ifwatchd.c
--- ifwatchd.c	2003/07/04 14:11:36	1.16
+++ ifwatchd.c	2003/10/19 04:33:16
@@ -64,11 +64,11 @@
 #include <netdb.h>
 #include <err.h>
 #include <ifaddrs.h>
 #include <syslog.h>
 
-enum event { ARRIVAL, DEPARTURE, UP, DOWN };
+enum event { ARRIVAL, DEPARTURE, UP, DOWN, CARRIER, NO_CARRIER };
 /* local functions */
 static void usage(void);
 static void dispatch(void*, size_t);
 static void check_addrs(char *cp, int addrs, enum event ev);
 static void invoke_script(struct sockaddr *sa, struct sockaddr *dst, enum event ev, int ifindex, const char *ifname_hint);
@@ -95,17 +95,21 @@ static int verbose = 0, quiet = 0;
 static int inhibit_initial = 0;
 static const char *arrival_script = NULL;
 static const char *departure_script = NULL;
 static const char *up_script = NULL;
 static const char *down_script = NULL;
+static const char *carrier_script = NULL;
+static const char *no_carrier_script = NULL;
 static char DummyTTY[] = _PATH_DEVNULL;
 static char DummySpeed[] = "9600";
 static const char **scripts[] = {
         &arrival_script,
         &departure_script,
         &up_script,
-        &down_script
+        &down_script,
+        &carrier_script,
+        &no_carrier_script
 };
 
 struct interface_data {
         SLIST_ENTRY(interface_data) next;
         int index;
@@ -119,11 +123,11 @@ main(int argc, char **argv)
         int c, s, n;
         int errs = 0;
         char msg[2048], *msgp;
 
         openlog(argv[0], LOG_PID|LOG_CONS, LOG_DAEMON);
-        while ((c = getopt(argc, argv, "qvhiu:d:A:D:")) != -1)
+        while ((c = getopt(argc, argv, "qvhic:n:u:d:A:D:")) != -1)
                 switch (c) {
                 case 'h':
                         usage();
                         return 0;
                 case 'i':
@@ -134,10 +138,18 @@ main(int argc, char **argv)
                         break;
                 case 'q':
                         quiet = 1;
                         break;
 
+                case 'c':
+                        carrier_script = optarg;
+                        break;
+
+                case 'n':
+                        no_carrier_script = optarg;
+                        break;
+
                 case 'u':
                         up_script = optarg;
                         break;
 
                 case 'd':
@@ -169,10 +181,12 @@ main(int argc, char **argv)
         if (verbose) {
                 printf("up_script: %s\ndown_script: %s\n",
                         up_script, down_script);
                 printf("arrival_script: %s\ndeparture_script: %s\n",
                         arrival_script, departure_script);
+                printf("carrier_script: %s\nno_carrier_script: %s\n",
+                        carrier_script, no_carrier_script);
                 printf("verbosity = %d\n", verbose);
         }
 
         while (argc > 0) {
                 list_interfaces(argv[0]);
@@ -213,30 +227,48 @@ static void
 usage()
 {
         fprintf(stderr, 
             "usage:\n"
             "\tifwatchd [-hiqv] [-A arrival-script] [-D departure-script]\n"
-            "\t\t  [-d down-script] [-u up-script] ifname(s)\n"
+            "\t\t  [-d down-script] [-u up-script]\n"
+            "\t\t  [-c carrier-script] [-n no-carrier-script] ifname(s)\n"
             "\twhere:\n"
             "\t -A <cmd> specify command to run on interface arrival event\n"
             "\t -D <cmd> specify command to run on interface departure event\n"
             "\t -d <cmd> specify command to run on interface down event\n"
             "\t -h       show this help message\n"
             "\t -i       no (!) initial run of the up script if the interface\n"
             "\t          is already up on ifwatchd startup\n"
             "\t -q       quiet mode, don't syslog informational messages\n"
             "\t -u <cmd> specify command to run on interface up event\n"
-            "\t -v       verbose/debug output, don't run in background\n");
+            "\t -c <cmd> specify command to run on interface carrier-detect event\n"
+            "\t -n <cmd> specify command to run on interface no-carrier-detect event\n"
+            "\t -v       verbose/debug output, don't run in background\n"
+            "\t          Multiple -v options increase verbosity.\n");
         exit(EXIT_FAILURE);
 }
 
 static void
 dispatch(void *msg, size_t len)
 {
         struct rt_msghdr *hd = msg;
         struct ifa_msghdr *ifam;
         enum event ev;
+        char *rtm_types[] = {"???", "add", "delete", "change", "get",
+                "losing", "redirect", "miss", "lock", "oldadd", "olddel",
+                "resolve", "newaddr", "deladdr", "oifinfo", "ifinfo",
+                "ifannounce"};
+        /* The following variables are used only for the RTM_IFINFO case. */
+        struct if_msghdr *ifmp;
+        static int last_carrier_status = -1;
+        int carrier_status;
+
+        /* With -v -v, this turns ifwatchd into a good route socket tracer. */
+        if (verbose > 1) {
+                printf("Got RTM message of type %d: %s\n", hd->rtm_type,
+                                rtm_types[hd->rtm_type]);
+        }
 
         switch (hd->rtm_type) {
         case RTM_NEWADDR:
                 ev = UP;
                 goto work;
@@ -245,13 +277,49 @@ dispatch(void *msg, size_t len)
                 goto work;
         case RTM_IFANNOUNCE:
                 rescan_interfaces();
                 check_announce((struct if_announcemsghdr *)msg);
                 return;
+        case RTM_IFINFO:
+                ifmp = (struct if_msghdr*)msg;
+                carrier_status = ifmp->ifm_data.ifi_link_state;
+                /* 
+                 * Treat it as an event worth handling if:
+                 * - the carrier status changed, or
+                 * - this is the first time we've been called, and
+                 * inhibit_initial is not set
+                 */
+                if ((carrier_status != last_carrier_status) ||
+                    ((last_carrier_status == -1) && !inhibit_initial)) {
+                        switch (ifmp->ifm_data.ifi_link_state) {
+                        case LINK_STATE_UP:
+                                ev = CARRIER;
+                                break;
+                        case LINK_STATE_DOWN:
+                                ev = NO_CARRIER;
+                                break;
+                        default:
+                                if (verbose) {
+                                        printf("unknown link status ignored\n");
+                                        return;
+                                }
+                                break;
+                        }
+                        invoke_script(NULL, NULL, ev, ifmp->ifm_index, NULL);
+                        last_carrier_status = carrier_status;
+                        return;
+                }
+                /* No change in status. */
+                if (verbose > 1) {
+                        printf("No change in carrier status.\n");
+                }
+                return;
+
+                break;
         }
         if (verbose)
-                printf("unknown message ignored\n");
+                printf("unknown message ignored: rtm_type %d\n", hd->rtm_type);
         return;
 
 work:
         ifam = (struct ifa_msghdr *)msg;
         check_addrs((char *)(ifam + 1), ifam->ifam_addrs, ev);
>Release-Note:
>Audit-Trail:
>Unformatted: