4 * Bring up a PPP connection and Do The Right Thing[tm] to make bringing
5 * the connection up or down with ifup and ifdown syncronous. Takes
6 * one argument: the logical name of the device to bring up. Does not
7 * detach until the interface is up or has permanently failed to come up.
9 * Copyright 1999-2001 Red Hat, Inc.
11 * This is free software; you can redistribute it and/or modify it
12 * under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful, but
17 * WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
30 * Register with netreport. (now exit implies deregister first)
31 * fork/exec ifup-ppp daemon <interface>
35 * if SIGTERM or SIGINT:
41 * wait for SIGCHLD to redial
43 * if no physical device found: continue
44 * elif physical device is down:
45 * wait for pppd to exit to redial if appropriate
46 * else: (physical device is up)
48 * if SIGCHLD: (pppd exited)
53 * else: (pppd was killed)
57 * When ppp-watch itself dies for reasons of its own, it uses a return code
58 * higher than 25 so as not to clash with pppd return codes, which, as of
59 * this writing, range from 0 to 19.
69 #include <sys/ioctl.h>
71 #include <sys/types.h>
72 #include <sys/resource.h>
73 #include <sys/socket.h>
81 #define IFCFGPREFIX "/etc/sysconfig/network-scripts/ifcfg-"
82 #define IFUP_PPP "/etc/sysconfig/network-scripts/ifup-ppp"
84 static int theSigterm = 0;
85 static int theSigint = 0;
86 static int theSighup = 0;
87 static int theSigio = 0;
88 static int theSigchld = 0;
89 static int theSigalrm = 0;
90 static int pipeArray[2];
92 // patch to respect the maxfail parameter to ppp
93 // Scott Sharkey <ssharkey@linux-no-limits.com>
94 static int dialCount = 0;
96 static void failureExit(int exitCode);
99 interrupt_child(int signo) {
100 kill(theChild, SIGINT);
104 set_signal(int signo, void (*handler)(int)) {
105 struct sigaction act;
106 act.sa_handler = handler;
107 act.sa_flags = SA_RESTART;
108 sigemptyset(&act.sa_mask);
109 sigaction(signo, &act, NULL);
112 /* Create a pipe, and fork off a child. This is the end of the road for
113 * the parent, which will wait for an exit status byte on the pipe (which
114 * is written by the child). */
116 detach(char *device) {
118 unsigned char exitCode;
121 if (pipe(pipeArray) == -1)
129 /* The parent only cares about notifications from the child. */
130 close (pipeArray[1]);
132 /* Certain signals are meant for our child, the watcher process. */
134 set_signal(SIGINT, interrupt_child);
135 set_signal(SIGTERM, interrupt_child);
136 set_signal(SIGHUP, interrupt_child);
138 /* Read the pipe until the child gives us an exit code as a byte. */
139 while (read (pipeArray[0], &exitCode, 1) == -1) {
141 case EINTR: continue;
142 default: exit (27); /* this will catch EIO in particular */
149 fprintf(stderr, "%s already up, initiating redial\n", device);
152 fprintf(stderr, "Failed to activate %s, retrying in the background\n", device);
155 fprintf(stderr, "Failed to activate %s with error %d\n", device, exitCode);
161 /* We're in the child process, which only writes the exit status
162 * of the pppd process to its parent (i.e., it reads nothing). */
163 close (pipeArray[0]);
165 /* Redirect stdio to /dev/null. */
166 fd = open("/dev/null", O_RDONLY);
167 dup2(fd, STDIN_FILENO);
170 fd = open("/dev/null", O_WRONLY);
171 dup2(fd, STDOUT_FILENO);
172 dup2(fd, STDERR_FILENO);
175 /* Become session and process group leader. */
180 /* Do magic with the pid file (/var/run/pppwatch-$DEVICE.pid):
181 * Try to open it for writing. If it exists, send a SIGHUP to whatever PID
182 * is already listed in it and remove it. Repeat until we can open it.
183 * Write out our PID, and return. */
185 doPidFile(char *device) {
186 static char pidFilePath[PATH_MAX] = "";
191 if (device == NULL) {
192 /* Remove an existing pid file -- we're exiting. */
193 if(strlen(pidFilePath) > 0) {
197 /* Set up the name of the pid file, used only the first time. */
198 snprintf(pidFilePath, sizeof(pidFilePath), "/var/run/pppwatch-%s.pid",
201 /* Create the pid file. */
203 fd = open(pidFilePath, O_WRONLY|O_TRUNC|O_CREAT|O_EXCL|O_NOFOLLOW,
204 S_IRUSR|S_IWUSR | S_IRGRP | S_IROTH);
206 /* Try to open the file for read. */
207 fd = open(pidFilePath, O_RDONLY);
209 failureExit(36); /* This is not good. */
211 /* We're already running, send a SIGHUP (we presume that they
212 * are calling ifup for a reason, so they probably want to
213 * redial) and then exit cleanly and let things go on in the
214 * background. Muck with the filename so that we don't go
215 * deleting the pid file for the already-running instance.
222 fscanf(f, "%d", &pid);
226 /* Try to kill it. */
227 if (kill(pid, SIGHUP) == -1) {
228 /* No such pid, remove the bogus pid file. */
231 /* Got it. Don't mess with the pid file on
233 memset(pidFilePath, '\0', sizeof(pidFilePath));
243 fprintf(f, "%d\n", getpid());
248 /* Fork off and exec() a child process. If reap_child is non-zero,
249 * wait for the child to exit and return 0 if it ran successfully,
250 * otherwise return 0 right away and let the SIGCHLD handler deal. */
252 fork_exec(gboolean reap, char *path, char *arg1, char *arg2, char *arg3)
264 /* Do the exec magic. Prepare by clearing the signal mask for pppd. */
266 sigprocmask(SIG_SETMASK, &sigs, NULL);
269 /* Make sure that the pppd is the leader for its process group. */
274 execl(path, path, arg1, arg2, arg3, NULL);
280 waitpid (childpid, &status, 0);
281 if (WIFEXITED(status) && (WEXITSTATUS(status) == 0)) {
291 /* Relay the pppd's exit code up to the parent -- can only be called once,
292 * because the parent exits as soon as it reads a byte. */
294 relay_exitcode(unsigned char code)
296 unsigned char exitCode;
298 write(pipeArray[1], &exitCode, 1);
302 /* Unregister with netreport, relay a status byte to the parent, clean up
303 * the pid file, and bail. */
305 failureExit(int exitCode) {
306 fork_exec(TRUE, "/sbin/netreport", "-r", NULL, NULL);
307 relay_exitcode(exitCode);
312 /* Keeps track of which signals we've seen so far. */
314 signal_tracker (int signum) {
317 theSigterm = 1; break;
319 theSigint = 1; break;
321 theSighup = 1; break;
325 theSigchld = 1; break;
327 theSigalrm = 1; break;
331 /* Return a shvarFile for this interface, taking into account one level of
332 * inheritance (eeewww). */
334 shvarfilesGet(const char *interfaceName) {
335 shvarFile *ifcfg = NULL;
336 char ifcfgName[PATH_MAX];
337 char *ifcfgParentDiff = NULL;
339 /* Start with the basic configuration. */
340 snprintf(ifcfgName, sizeof(ifcfgName), "%s%s", IFCFGPREFIX, interfaceName);
341 ifcfg = svNewFile(ifcfgName);
345 /* Do we have a parent interface (i.e., for ppp0-blah, ppp0) to inherit? */
346 ifcfgParentDiff = strchr(ifcfgName + sizeof(IFCFGPREFIX), '-');
347 if (ifcfgParentDiff) {
348 *ifcfgParentDiff = '\0';
349 ifcfg->parent = svNewFile(ifcfgName);
352 /* This is very unclean, but we have to close the shvar descriptors in
353 * case they've been numbered STDOUT_FILENO or STDERR_FILENO, which would
354 * be disastrous if inherited by a child process. */
359 close (ifcfg->parent->fd);
360 ifcfg->parent->fd = 0;
366 /* Convert a logical interface name to a real one by reading the lock
367 * file created by pppd. */
369 pppLogicalToPhysical(int *pppdPid, char *logicalName, char **physicalName) {
370 char mapFileName[PATH_MAX];
374 char *physicalDevice = NULL;
376 snprintf(mapFileName, sizeof(mapFileName), "/var/run/ppp-%s.pid",
378 fd = open(mapFileName, O_RDONLY);
380 n = read(fd, buffer, sizeof(buffer));
384 /* Split up the file at the first line break -- the PID is on the
386 if((p = strchr(buffer, '\n')) != NULL) {
390 *pppdPid = atoi(buffer);
392 /* The physical device name is on the second line. */
393 if((q = strchr(p, '\n')) != NULL) {
395 physicalDevice = strdup(p);
401 if (physicalDevice) {
403 *physicalName = physicalDevice;
405 free(physicalDevice);
409 *physicalName = NULL;
414 /* Return a boolean value indicating if the interface is up. If not, or
415 * if we don't know, return FALSE. */
417 interfaceIsUp(char *device) {
419 int family[] = {PF_INET, PF_IPX, PF_AX25, PF_APPLETALK, 0};
422 gboolean retcode = FALSE;
424 /* Create a socket suitable for doing routing ioctls. */
425 for (p = 0; (sock == -1) && family[p]; p++) {
426 sock = socket(family[p], SOCK_DGRAM, 0);
431 /* Populate the request structure for getting the interface's status. */
432 memset(&ifr, 0, sizeof(ifr));
433 strncpy(ifr.ifr_name, device, sizeof(ifr.ifr_name) - 1);
434 ifr.ifr_name[sizeof(ifr.ifr_name) - 1] = '\0';
436 /* We return TRUE iff the ioctl succeeded and the interface is UP. */
437 if (ioctl(sock, SIOCGIFFLAGS, &ifr) == -1) {
439 } else if (ifr.ifr_flags & IFF_UP) {
448 /* Very, very minimal hangup function. This just attempts to hang up a device
449 * that should already be hung up, so it does not need to be bulletproof. */
451 hangup(shvarFile *ifcfg) {
454 struct termios original_ts, ts;
456 line = svGetValue(ifcfg, "MODEMPORT");
460 fd = open(line, O_RDWR | O_NOCTTY | O_NONBLOCK);
462 if (tcgetattr(fd, &ts) != -1) {
464 write(fd, "\r", 1); /* tickle modems that do not like dropped DTR */
466 cfsetospeed(&ts, B0);
467 tcsetattr(fd, TCSANOW, &ts);
469 tcsetattr(fd, TCSANOW, &original_ts);
477 main(int argc, char **argv) {
480 char *device, *real_device, *physicalDevice = NULL;
483 sigset_t blockedsigs, unblockedsigs;
487 gboolean dying = FALSE;
489 gboolean connectedOnce = FALSE;
490 int maxfail = 0; // MAXFAIL Patch <ssharkey@linux-no-limits.com>
493 fprintf (stderr, "usage: ppp-watch [ifcfg-]<logical-name> [boot]\n");
497 if (strncmp(argv[1], "ifcfg-", 6) == 0) {
498 device = argv[1] + 6;
503 detach(device); /* Prepare a child process to monitor pppd. When we
504 return, we'll be in the child. */
506 if ((argc > 2) && (strcmp("boot", argv[2]) == 0)) {
510 ifcfg = shvarfilesGet(device);
514 real_device = svGetValue(ifcfg, "DEVICE");
515 if (real_device == NULL)
516 real_device = device;
518 doPidFile(real_device);
520 /* We'll want to know which signal interrupted our sleep below, so
521 * attach a signal handler to these. */
522 set_signal(SIGTERM, signal_tracker);
523 set_signal(SIGINT, signal_tracker);
524 set_signal(SIGHUP, signal_tracker);
525 set_signal(SIGIO, signal_tracker);
526 set_signal(SIGCHLD, signal_tracker);
528 /* We time out only if we're being run at boot-time. */
530 temp = svGetValue(ifcfg, "BOOTTIMEOUT");
532 timeout = atoi(temp);
533 if (timeout < 1) timeout = 1;
538 set_signal(SIGALRM, signal_tracker);
542 /* Register us to get a signal when something changes. Yes, that's vague. */
543 fork_exec(TRUE, "/sbin/netreport", NULL, NULL, NULL);
545 /* Reset theSigchld, which should have been triggered by netreport. */
548 /* We don't set up the procmask until after we have received the netreport
549 * signal. Do so now. */
550 sigemptyset(&blockedsigs);
551 sigaddset(&blockedsigs, SIGTERM);
552 sigaddset(&blockedsigs, SIGINT);
553 sigaddset(&blockedsigs, SIGHUP);
554 sigaddset(&blockedsigs, SIGIO);
555 sigaddset(&blockedsigs, SIGCHLD);
557 sigaddset(&blockedsigs, SIGALRM);
559 sigprocmask(SIG_BLOCK, &blockedsigs, NULL);
561 sigfillset(&unblockedsigs);
562 sigdelset(&unblockedsigs, SIGTERM);
563 sigdelset(&unblockedsigs, SIGINT);
564 sigdelset(&unblockedsigs, SIGHUP);
565 sigdelset(&unblockedsigs, SIGIO);
566 sigdelset(&unblockedsigs, SIGCHLD);
568 sigdelset(&unblockedsigs, SIGALRM);
570 sigprocmask(SIG_UNBLOCK, &unblockedsigs, NULL);
572 /* Initialize the retry timeout using the RETRYTIMEOUT setting. */
573 temp = svGetValue(ifcfg, "RETRYTIMEOUT");
575 timeout = atoi(temp);
581 /* Start trying to bring the interface up. */
582 fork_exec(FALSE, IFUP_PPP, "daemon", device, boot);
585 /* Wait for a signal. */
592 sigsuspend(&unblockedsigs);
595 /* If we got SIGTERM or SIGINT, give up and hang up. */
596 if (theSigterm || theSigint) {
597 theSigterm = theSigint = 0;
599 /* If we've already tried to exit this way, use SIGKILL instead
600 * of SIGTERM, because pppd's just being stubborn. */
608 /* Get the pid of our child pppd. */
609 pppLogicalToPhysical(&pppdPid, real_device, NULL);
611 /* We don't know what our child pid is. This is very confusing. */
616 /* Die, pppd, die. */
617 kill(pppdPid, sendsig);
618 if (sendsig == SIGKILL) {
619 kill(-pppdPid, SIGTERM); /* Give it a chance to die nicely, then
620 kill its whole process group. */
622 kill(-pppdPid, sendsig);
628 /* If we got SIGHUP, reload and redial. */
632 /* Free and reload the configuration structure. */
634 svCloseFile(ifcfg->parent);
636 ifcfg = shvarfilesGet(device);
638 /* Get the PID of our child pppd. */
639 pppLogicalToPhysical(&pppdPid, real_device, NULL);
640 kill(pppdPid, SIGTERM);
642 /* We'll redial when the SIGCHLD arrives, even if PERSIST is
643 * not set (the latter handled by clearing the "we've connected
644 * at least once" flag). */
645 connectedOnce = FALSE;
647 /* We don't want to delay before redialing, either, so cut
648 * the retry timeout to zero. */
652 /* If we got a SIGIO (from netreport, presumably), check if the
653 * interface is up and return zero (via our parent) if it is. */
657 pppLogicalToPhysical(NULL, real_device, &physicalDevice);
658 if (physicalDevice) {
659 if (interfaceIsUp(physicalDevice)) {
660 /* The interface is up, so report a success to a parent if
661 * we have one. Any errors after this we just swallow. */
663 connectedOnce = TRUE;
665 free(physicalDevice);
669 /* If we got a SIGCHLD, then pppd died (possibly because we killed it),
670 * and we need to restart it after timeout seconds. */
674 /* Find its pid, which is also its process group ID. */
675 waited = waitpid(-1, &status, 0);
680 /* Now, we need to kill any children of pppd still in pppd's
681 * process group, in case they are hanging around.
682 * pppd is dead (we just waited for it) but there is no
683 * guarantee that its children are dead, and they will
684 * hold the modem if we do not get rid of them.
685 * We have kept the old pid/pgrp around in pppdPid. */
687 kill(-pppdPid, SIGTERM); /* give it a chance to die nicely */
689 kill(-pppdPid, SIGKILL);
694 /* Bail if the child exitted abnormally or we were already
695 * signalled to kill it. */
696 if (!WIFEXITED(status)) {
700 failureExit(WEXITSTATUS(status));
703 /* Error conditions from which we do not expect to recover
704 * without user intervention -- do not fill up the logs. */
705 switch (WEXITSTATUS(status)) {
706 case 1: case 2: case 3: case 4: case 6:
707 case 7: case 9: case 14: case 17:
708 failureExit(WEXITSTATUS(status));
714 /* We default to retrying the connect phase for backward
715 * compatibility, unless RETRYCONNECT is false. */
716 if ((WEXITSTATUS(status) == 8) &&
717 !svTrueValue(ifcfg, "RETRYCONNECT", TRUE)) {
718 failureExit(WEXITSTATUS(status));
721 /* If we've never connected, or PERSIST is set, dial again, up
722 * to MAXFAIL times. */
723 if ((WEXITSTATUS(status) == 8) ||
725 svTrueValue(ifcfg, "PERSIST", FALSE)) {
726 /* If we've been connected (i.e., if we didn't force a redial,
727 * but the connection went down) wait for DISCONNECTTIMEOUT
728 * seconds before redialing. */
730 connectedOnce = FALSE;
731 temp = svGetValue(ifcfg, "DISCONNECTTIMEOUT");
733 timeout = atoi(temp);
739 sigprocmask(SIG_UNBLOCK, &blockedsigs, NULL);
741 sigprocmask(SIG_BLOCK, &blockedsigs, NULL);
747 fork_exec(FALSE, IFUP_PPP, "daemon", device, boot);
749 /* Reinitialize the retry timeout. */
750 temp = svGetValue(ifcfg, "RETRYTIMEOUT");
752 timeout = atoi(temp);
757 // Scott Sharkey <ssharkey@linux-no-limits.com>
759 temp = svGetValue(ifcfg, "MAXFAIL");
761 maxfail = atoi(temp);
766 if ( maxfail != 0 ) {
768 if ( dialCount < maxfail ) {
769 fork_exec(FALSE, IFUP_PPP, "daemon", device, boot);
771 failureExit(WEXITSTATUS(status));
774 fork_exec(FALSE, IFUP_PPP, "daemon", device, boot);
777 failureExit(WEXITSTATUS(status));
781 /* We timed out, and we're running at boot-time. */