]> git.ozlabs.org Git - ppp.git/commitdiff
Merge pull request #367 from jkroonza/consolidated-ifup-down-and-scripts
authorPaul Mackerras <paulus@ozlabs.org>
Wed, 11 Oct 2023 00:44:36 +0000 (11:44 +1100)
committerPaul Mackerras <paulus@ozlabs.org>
Wed, 11 Oct 2023 00:44:36 +0000 (11:44 +1100)
Implement net-init, net-pre-up and net-down scripts in pppd.

14 files changed:
pppd/Makefile.am
pppd/lcp.c
pppd/pathnames.h
pppd/plugins/pppoe/if.c
pppd/plugins/pppoe/pppoe-discovery.8
pppd/plugins/radius/pppd-radattr.8
pppd/plugins/radius/pppd-radius.8
pppd/pppd-private.h
pppd/pppd.8
pppd/tdb.c
pppd/utils.c
pppd/utils_utest.c [new file with mode: 0644]
scripts/lcp_rtt_dump [new file with mode: 0755]
scripts/lcp_rtt_exporter [new file with mode: 0644]

index e5bedf26af963325edfb9852864249a0765b42bd..c5fe10776ede7daf777e1e4f9edfa7623d26d30d 100644 (file)
@@ -20,6 +20,12 @@ utest_pppcrypt_LDFLAGS =
 
 check_PROGRAMS += utest_crypto
 
+utest_utils_SOURCES = utils.c utils_utest.c
+utest_utils_CPPFLAGS = -DUNIT_TEST
+utest_utils_LDFLAGS =
+
+check_PROGRAMS += utest_utils
+
 if WITH_SRP
 sbin_PROGRAMS += srp-entry
 dist_man8_MANS += srp-entry.8
@@ -83,7 +89,7 @@ pppd_SOURCES = \
     upap.c \
     utils.c
 
-pppd_CPPFLAGS = -DSYSCONFDIR=\"${sysconfdir}\" -DLOCALSTATEDIR=\"${localstatedir}\" -DPPPD_RUNTIME_DIR='"@PPPD_RUNTIME_DIR@"' -DPPPD_LOGFILE_DIR='"@PPPD_LOGFILE_DIR@"'
+pppd_CPPFLAGS = -DSYSCONFDIR=\"${sysconfdir}\" -DPPPD_RUNTIME_DIR='"@PPPD_RUNTIME_DIR@"' -DPPPD_LOGFILE_DIR='"@PPPD_LOGFILE_DIR@"'
 pppd_LDFLAGS =
 pppd_LIBS =
 
index c72c2ef90bb2dc10cc3970b08059ea11344016e0..d174b8fb53066e14752d0dac37ce5d1663bf28f0 100644 (file)
 #include <stdio.h>
 #include <string.h>
 #include <stdlib.h>
+#include <fcntl.h>
+#include <string.h>
+#include <time.h>
+#include <arpa/inet.h>
+#include <sys/mman.h>
 
 #include "pppd-private.h"
 #include "options.h"
 
 static void lcp_delayed_up(void *);
 
+/*
+ * These definitions relate to the measurement and logging of round-trip
+ * time (RTT) of LCP echo-requests implemented in lcp_rtt_update_buffer().
+ */
+#define LCP_RTT_MAGIC 0x19450425
+#define LCP_RTT_HEADER_LENGTH 4
+#define LCP_RTT_FILE_SIZE 8192
+#define LCP_RTT_ELEMENTS (LCP_RTT_FILE_SIZE / sizeof(u_int32_t) - LCP_RTT_HEADER_LENGTH) / 2
+
 /*
  * LCP-related command-line options.
  */
 int    lcp_echo_interval = 0;  /* Interval between LCP echo-requests */
 int    lcp_echo_fails = 0;     /* Tolerance to unanswered echo-requests */
 bool   lcp_echo_adaptive = 0;  /* request echo only if the link was idle */
+char   *lcp_rtt_file = NULL;   /* measure the RTT of LCP echo-requests */
 bool   lax_recv = 0;           /* accept control chars in asyncmap */
 bool   noendpoint = 0;         /* don't send/accept endpoint discriminator */
 
@@ -152,6 +167,9 @@ static struct option lcp_option_list[] = {
       "Set time in seconds between LCP echo requests", OPT_PRIO },
     { "lcp-echo-adaptive", o_bool, &lcp_echo_adaptive,
       "Suppress LCP echo requests if traffic was received", 1 },
+    { "lcp-rtt-file", o_string, &lcp_rtt_file,
+      "Filename for logging the round-trip time of LCP echo requests",
+      OPT_PRIO | OPT_PRIV },
     { "lcp-restart", o_int, &lcp_fsm[0].timeouttime,
       "Set time in seconds between LCP retransmissions", OPT_PRIO },
     { "lcp-max-terminate", o_int, &lcp_fsm[0].maxtermtransmits,
@@ -197,6 +215,8 @@ lcp_options lcp_hisoptions[NUM_PPP];        /* Options that we ack'd */
 static int lcp_echos_pending = 0;      /* Number of outstanding echo msgs */
 static int lcp_echo_number   = 0;      /* ID number of next echo frame */
 static int lcp_echo_timer_running = 0;  /* set if a timer is running */
+static int lcp_rtt_file_fd = 0;                /* fd for the opened LCP RTT file */
+static volatile u_int32_t *lcp_rtt_buffer = NULL; /* the mmap'ed LCP RTT file */
 
 static u_char nak_buffer[PPP_MRU];     /* where we construct a nak packet */
 
@@ -2225,6 +2245,72 @@ LcpEchoTimeout (void *arg)
     }
 }
 
+/*
+ * Log the round-trip time (RTT) of the received LCP echo-request.
+ *
+ * The header section at the beginning of lcp_rtt_file contains
+ * LCP_RTT_HEADER_LENGTH fields, each a u_int32_t in network byte order:
+ * [0] LCP_RTT_MAGIC
+ * [1] status (1: the file is open and is being written)
+ * [2] index of the most recently updated element
+ * [3] the value of the lcp-echo-interval parameter
+ *
+ * The header is followed by a ring buffer of LCP_RTT_ELEMENTS elements, each
+ * containing a pair of u_int32_t in network byte order with this content:
+ * [0] UNIX timestamp
+ * [1] bits 24-31: the number of lost LCP echo replies
+ *     bits 0-23:  the measured RTT in microseconds
+ *
+ * The timestamp is unsigned to support storing dates beyond 2038.
+ *
+ * Consumers of lcp_rtt_file are expected to:
+ * - read the complete file of arbitrary length
+ * - check the magic number
+ * - process the data elements starting at the index
+ * - ignore any elements with a timestamp of 0
+ */
+static void
+lcp_rtt_update_buffer (unsigned long rtt)
+{
+    volatile u_int32_t *const ring_buffer = lcp_rtt_buffer
+       + LCP_RTT_HEADER_LENGTH;
+    unsigned int next_entry, lost;
+
+    /* choose the next entry where the data will be stored */
+    if (ntohl(lcp_rtt_buffer[2]) >= (LCP_RTT_ELEMENTS - 1) * 2)
+       next_entry = 0;                         /* go back to the beginning */
+    else
+       next_entry = ntohl(lcp_rtt_buffer[2]) + 2;      /* use the next one */
+
+    /* update the data element */
+    /* storing the timestamp in an *unsigned* long allows dates up to 2106 */
+    ring_buffer[next_entry] = htonl((u_int32_t) time(NULL));
+    lost = lcp_echos_pending - 1;
+    if (lost > 0xFF)
+       lost = 0xFF;            /* truncate the lost packets count to 256 */
+    if (rtt > 0xFFFFFF)
+       rtt = 0xFFFFFF;         /* truncate the RTT to 16777216 */
+    /* use bits 24-31 for the lost packets count and bits 0-23 for the RTT */
+    ring_buffer[next_entry + 1] = htonl((u_int32_t) ((lost << 24) + rtt));
+
+    /* update the pointer to the (just updated) most current data element */
+    lcp_rtt_buffer[2] = htonl(next_entry);
+
+    /* In theory, CPUs implementing a weakly-consistent memory model do not
+     * guarantee that these three memory store operations to the buffer will
+     * be seen in the same order by the reader process.
+     * This means that a process reading the file could see the index
+     * having been updated before the element that the index points to had
+     * been written.
+     * But in practice we expect that the read(2) system call used by
+     * consumers processes is atomic with respect to the following msync(2)
+     * call, so we ignore the issue.
+     */
+
+    if (msync(lcp_rtt_buffer, LCP_RTT_FILE_SIZE, MS_ASYNC) < 0)
+       error("msync() for %s failed: %m", lcp_rtt_file);
+}
+
 /*
  * LcpEchoReply - LCP has received a reply to the echo
  */
@@ -2246,6 +2332,30 @@ lcp_received_echo_reply (fsm *f, int id, u_char *inp, int len)
        return;
     }
 
+    if (lcp_rtt_file_fd && len >= 16) {
+       long lcp_rtt_magic;
+
+       /*
+        * If the magic word is found at the beginning of the data section
+        * of the frame then read the timestamp which follows and subtract
+        * it from the current time to compute the round trip time.
+        */
+       GETLONG(lcp_rtt_magic, inp);
+       if (lcp_rtt_magic == LCP_RTT_MAGIC) {
+           struct timespec ts;
+           unsigned long req_sec, req_nsec, rtt;
+
+           clock_gettime(CLOCK_MONOTONIC, &ts);
+           GETLONG(req_sec, inp);
+           GETLONG(req_nsec, inp);
+           /* compute the RTT in microseconds */
+           rtt = (ts.tv_sec - req_sec) * 1000000
+               + (ts.tv_nsec / 1000 - req_nsec / 1000);
+           /* log the RTT */
+           lcp_rtt_update_buffer(rtt);
+       }
+    }
+
     /* Reset the number of outstanding echo frames */
     lcp_echos_pending = 0;
 }
@@ -2258,7 +2368,7 @@ static void
 LcpSendEchoRequest (fsm *f)
 {
     u_int32_t lcp_magic;
-    u_char pkt[4], *pktp;
+    u_char pkt[16], *pktp;
 
     /*
      * Detect the failure of the peer at this point.
@@ -2293,11 +2403,68 @@ LcpSendEchoRequest (fsm *f)
         lcp_magic = lcp_gotoptions[f->unit].magicnumber;
        pktp = pkt;
        PUTLONG(lcp_magic, pktp);
+
+       /* Put a timestamp in the data section of the frame */
+       if (lcp_rtt_file_fd) {
+           struct timespec ts;
+
+           PUTLONG(LCP_RTT_MAGIC, pktp);
+           clock_gettime(CLOCK_MONOTONIC, &ts);
+           PUTLONG((u_int32_t)ts.tv_sec, pktp);
+           PUTLONG((u_int32_t)ts.tv_nsec, pktp);
+       }
+
         fsm_sdata(f, ECHOREQ, lcp_echo_number++ & 0xFF, pkt, pktp - pkt);
        ++lcp_echos_pending;
     }
 }
 
+static void
+lcp_rtt_open_file (void)
+{
+    if (!lcp_rtt_file)
+       return;
+
+    lcp_rtt_file_fd = open(lcp_rtt_file, O_RDWR | O_CREAT, 0644);
+    if (lcp_rtt_file_fd < 0) {
+       error("Can't open the RTT log file %s: %m", lcp_rtt_file);
+       lcp_rtt_file_fd = 0;
+       return;
+    }
+
+    if (ftruncate(lcp_rtt_file_fd, LCP_RTT_FILE_SIZE) < 0)
+       fatal("ftruncate() of %s failed: %m", lcp_rtt_file);
+    lcp_rtt_buffer = mmap(0, LCP_RTT_FILE_SIZE, PROT_READ | PROT_WRITE,
+           MAP_SHARED, lcp_rtt_file_fd, 0);
+    if (lcp_rtt_buffer == MAP_FAILED)
+       fatal("mmap() of %s failed: %m", lcp_rtt_file);
+
+    /* initialize the ring buffer */
+    if (lcp_rtt_buffer[0] != htonl(LCP_RTT_MAGIC)) {
+       memset(lcp_rtt_buffer, 0, LCP_RTT_FILE_SIZE);
+       lcp_rtt_buffer[0] = htonl(LCP_RTT_MAGIC);
+    }
+
+    lcp_rtt_buffer[3] = htonl(lcp_echo_interval);
+    lcp_rtt_buffer[1] = htonl(1); /* status: LCP up, file opened */
+}
+
+static void
+lcp_rtt_close_file (void)
+{
+    if (!lcp_rtt_file_fd)
+       return;
+
+    lcp_rtt_buffer[1] = htonl(0); /* status: LCP down, file closed */
+
+    if (munmap(lcp_rtt_buffer, LCP_RTT_FILE_SIZE) < 0)
+       error("munmap() of %s failed: %m", lcp_rtt_file);
+    if (close(lcp_rtt_file_fd) < 0)
+       error("close() of %s failed: %m", lcp_rtt_file);
+    lcp_rtt_buffer = NULL;
+    lcp_rtt_file_fd = 0;
+}
+
 /*
  * lcp_echo_lowerup - Start the timer for the LCP frame
  */
@@ -2311,6 +2478,9 @@ lcp_echo_lowerup (int unit)
     lcp_echos_pending      = 0;
     lcp_echo_number        = 0;
     lcp_echo_timer_running = 0;
+
+    /* Open the file where the LCP RTT data will be logged */
+    lcp_rtt_open_file();
   
     /* If a timeout interval is specified then start the timer */
     if (lcp_echo_interval != 0)
@@ -2330,4 +2500,7 @@ lcp_echo_lowerdown (int unit)
         UNTIMEOUT (LcpEchoTimeout, f);
         lcp_echo_timer_running = 0;
     }
+
+    /* Close the file containing the LCP RTT data */
+    lcp_rtt_close_file();
 }
index 439c6c8e21da0c2ea0ed3022473df27ddf4c61cd..e50132f5962191d7a20991c718b900f3ba518ef2 100644 (file)
 #define PPP_PATH_PPPDB          PPP_PATH_VARRUN  "/pppd2.tdb"
 
 #ifdef __linux__
-#define PPP_PATH_LOCKDIR        PPP_PATH_VARRUN  "/lock"
+#define PPP_PATH_LOCKDIR        "/var/lock"
 #else
 #ifdef SVR4
-#define PPP_PATH_LOCKDIR        LOCALSTATEDIR "/spool/locks"
+#define PPP_PATH_LOCKDIR        "/var/spool/locks"
 #else
-#define PPP_PATH_LOCKDIR        LOCALSTATEDIR "/spool/lock"
+#define PPP_PATH_LOCKDIR        "/var/spool/lock"
 #endif
 #endif
 
index 87e5f6ec87921e1dd93fff200b2d1d78a1a8d314..4b13ecc19866eed617fa8ce038d8b2b728ceeba2 100644 (file)
@@ -175,7 +175,7 @@ openInterface(char const *ifname, UINT16_t type, unsigned char *hwaddr)
     sa.sll_ifindex = ifr.ifr_ifindex;
 
 #else
-    strcpy(sa.sa_data, ifname);
+    strlcpy(sa.sa_data, ifname, sizeof(sa.sa_data));
 #endif
 
     /* We're only interested in packets on specified interface */
@@ -212,7 +212,7 @@ sendPacket(PPPoEConnection *conn, int sock, PPPoEPacket *pkt, int size)
 #else
     struct sockaddr sa;
 
-    strcpy(sa.sa_data, conn->ifName);
+    strlcpy(sa.sa_data, conn->ifName, sizeof(sa.sa_data));
     err = sendto(sock, pkt, size, 0, &sa, sizeof(sa));
 #endif
     if (err < 0) {
index 523ef8c47e383722678149095e0b66fdb8111b14..3ea62d1cb4d9e79930ce46aa0c712bfc50ba1ed6 100644 (file)
@@ -83,4 +83,4 @@ version number and usage information, then exit.
 \fBpppoe\-discovery\fR was written by Marco d'Itri <md@linux.it>,
 based on \fBpppoe\fR by Dianne Skoll <dianne@skoll.ca>.
 .SH SEE ALSO
-pppoe(8), pppoe-sniff(8)
+pppoe(8), pppoe\-sniff(8)
index bcaad3b28c3c5fe84632e07f65922fcb03358ec0..1346fdd30df01c95e3cec4ab84675112009dc760 100644 (file)
@@ -24,7 +24,7 @@ where
 .I pppN
 is the name of the PPP interface.  The RADIUS attributes are stored
 one per line in the format "Attribute-Name Attribute-Value".  This
-format is convenient for use in /etc/ppp/ip-up and /etc/ppp/ip-down
+format is convenient for use in /etc/ppp/ip\-up and /etc/ppp/ip\-down
 scripts.
 .LP
 Note that you
@@ -38,7 +38,7 @@ To use the plugin, simply supply the
 options to pppd.
 
 .SH SEE ALSO
-.BR pppd (8) " pppd-radius" (8)
+.BR pppd (8) " pppd\-radius" (8)
 
 .SH AUTHOR
 Dianne Skoll <dianne@skoll.ca>
index 08b97fbffc7457c59f7554734611434807bb963c..8628d08c3667a00cf15fd412a33afc0dbada924f 100644 (file)
@@ -19,9 +19,9 @@ plugin radius.so
 .LP
 The RADIUS plugin for pppd permits pppd to perform PAP, CHAP, MS-CHAP and
 MS-CHAPv2 authentication against a RADIUS server instead of the usual
-.I /etc/ppp/pap-secrets
+.I /etc/ppp/pap\-secrets
 and
-.I /etc/ppp/chap-secrets
+.I /etc/ppp/chap\-secrets
 files.
 .LP
 The RADIUS plugin is built on a library called
@@ -33,7 +33,7 @@ plugin
 .SH OPTIONS
 The RADIUS plugin introduces one additional pppd option:
 .TP
-.BI "radius-config-file " filename
+.BI "radius\-config\-file " filename
 The file
 .I filename
 is taken as the radiusclient configuration file.  If this option is not
@@ -44,10 +44,10 @@ as the configuration file.
 .BI "avpair " attribute=value
 Adds an Attribute-Value pair to be passed on to the RADIUS server on each request.
 .TP
-.BI map-to-ifname
+.BI map\-to\-ifname
 Sets Radius NAS-Port attribute to number equal to interface name (Default)
 .TP
-.BI map-to-ttyname
+.BI map\-to\-ttyname
 Sets Radius NAS-Port attribute value via libradiusclient library
 
 .SH USAGE
@@ -61,7 +61,7 @@ RADIUS server should assign an IP address to the peer using the RADIUS
 Framed-IP-Address attribute.
 
 .SH SEE ALSO
-.BR pppd (8) " pppd-radattr" (8)
+.BR pppd (8) " pppd\-radattr" (8)
 
 .SH AUTHOR
 Dianne Skoll <dianne@skoll.ca>
index 2883e4622acbc740c31da793a6c35e15c64890f6..46ce0c8bdceba2898ba2ad93b76201e647104272 100644 (file)
@@ -437,6 +437,7 @@ int  sifproxyarp(int, u_int32_t);
 int  cifproxyarp(int, u_int32_t);
                                /* Delete proxy ARP entry for peer */
 u_int32_t GetMask(u_int32_t); /* Get appropriate netmask for address */
+int  mkdir_recursive(const char *); /* Recursively create directory */
 int  lock(char *);     /* Create lock file for device */
 int  relock(int);              /* Rewrite lock file with new pid */
 void unlock(void);     /* Delete previously-created lock file */
index f66ca891e10c106130a0845fc088a996201cf503..7be3e8074fe2502a9b7efc23bf5fcc0e535298c6 100644 (file)
@@ -637,6 +637,10 @@ Set the maximum number of LCP terminate-request transmissions to
 Set the LCP restart interval (retransmission timeout) to \fIn\fR
 seconds (default 3).
 .TP
+.B lcp\-rtt\-file \fIfilename
+Sets the file where the round-trip time (RTT) of LCP echo-request frames
+will be logged.
+.TP
 .B linkname \fIname\fR
 Sets the logical name of the link to \fIname\fR.  Pppd will create a
 file named \fBppp\-\fIname\fB.pid\fR in /var/run (or /etc/ppp on some
@@ -687,7 +691,7 @@ network control protocol comes up).
 Terminate after \fIn\fR consecutive failed connection attempts.  A
 value of 0 means no limit.  The default value is 10.
 .TP
-.B max-tls-version \fIstring
+.B max\-tls-\version \fIstring
 (EAP-TLS, or PEAP) Configures the max allowed TLS version used during
 negotiation with a peer.  The default value for this is \fI1.2\fR.  Values
 allowed for this option is \fI1.0.\fR, \fI1.1\fR, \fI1.2\fR, \fI1.3\fR.
@@ -760,7 +764,7 @@ name to \fIname\fR.)
 Disable Address/Control compression in both directions (send and
 receive).
 .TP
-.B need-peer-eap
+.B need\-peer\-eap
 (EAP-TLS) Require the peer to verify our authentication credentials.
 .TP
 .B noauth
@@ -1136,13 +1140,13 @@ The device used by pppd with this option must have sync support.
 Currently supports Microgate SyncLink adapters
 under Linux and FreeBSD 2.2.8 and later.
 .TP
-.B tls-verify-method \fIstring
+.B tls\-verify\-method \fIstring
 (EAP-TLS, or PEAP) Match the value specified for \fIremotename\fR to that that
 of the X509 certificates subject name, common name, or suffix of the common
 name.  Respective values allowed for this option is: \fInone\fR, \fIsubject\fR,
 \fIname\fR, or \fIsuffix\fR.  The default value for this option is \fIname\fR.
 .TP
-.B tls-verify-key-usage
+.B tls\-verify\-key\-usage
 (EAP-TLS, or PEAP) Enables examination of peer certificate's purpose, and
 extended key usage attributes.
 .TP
@@ -1233,36 +1237,36 @@ by specifying ppp'd option \fBnic-eth0\fR. Prefix \fBnic-\fR for this
 option may be avoided if interface name is unambiguous and does not
 look like any other pppd's option.
 .TP
-.B pppoe-service \fIname
+.B pppoe\-service \fIname
 Connect to specified PPPoE service name. For backward compatibility also
 \fBrp_pppoe_service\fP option name is supported.
 .TP
-.B pppoe-ac \fIname
+.B pppoe\-ac \fIname
 Connect to specified PPPoE access concentrator name. For backward
 compatibility also \fBrp_pppoe_ac\fP option name is supported.
 .TP
-.B pppoe-sess \fIsessid\fP:\fImacaddr
+.B pppoe\-sess \fIsessid\fP:\fImacaddr
 Attach to existing PPPoE session. For backward compatibility also
 \fBrp_pppoe_sess\fP option name is supported.
 .TP
-.B pppoe-verbose \fIn
+.B pppoe\-verbose \fIn
 Be verbose about discovered access concentrators. When set to 2 or bigger
 value then dump also discovery packets. For backward compatibility also
 \fBrp_pppoe_verbose\fP option name is supported.
 .TP
-.B pppoe-mac \fImacaddr
+.B pppoe\-mac \fImacaddr
 Connect to specified MAC address.
 .TP
-.B pppoe-host-uniq \fIstring
+.B pppoe\-host\-uniq \fIstring
 Set the PPPoE Host-Uniq tag to the supplied hex string.
 By default PPPoE Host-Uniq tag is set to the pppd's process PID.
 For backward compatibility this option may be specified without
 \fBpppoe-\fP prefix.
 .TP
-.B pppoe-padi-timeout \fIn
+.B pppoe\-padi\-timeout \fIn
 Initial timeout for discovery packets in seconds (default 5).
 .TP
-.B pppoe-padi-attempts \fIn
+.B pppoe\-padi\-attempts \fIn
 Number of discovery attempts (default 3).
 .SH OPTIONS FILES
 Options can be taken from files as well as the command line.  Pppd
@@ -1729,8 +1733,8 @@ We failed to authenticate ourselves to the peer.
 Pppd invokes scripts at various stages in its processing which can be
 used to perform site-specific ancillary processing.  These scripts are
 usually shell scripts, but could be executable code files instead.
-Pppd does not wait for the scripts to finish (except for the net-init,
-net-pre-up and ip-pre-up scripts).  The scripts are
+Pppd does not wait for the scripts to finish (except for the net\-init,
+net\-pre\-up and ip\-pre\-up scripts).  The scripts are
 executed as root (with the real and effective user-id set to 0), so
 that they can do things such as update routing tables or run
 privileged daemons.  Be careful that the contents of these scripts do
@@ -2104,7 +2108,7 @@ are met:
 .br
      (412) 268-4387, fax: (412) 268-7395
 .br
-     tech-transfer@andrew.cmu.edu
+     tech\-transfer@andrew.cmu.edu
 .LP
 3b. The name(s) of the authors of this software must not be used to
    endorse or promote products derived from this software without
index 8a563f88b9a62b8aedc2ea504f8f8b22afdddb95..62644171aa6ed8eee736bcde7e9ff13350207b01 100644 (file)
 #include <sys/mman.h>
 #include <sys/stat.h>
 #include <signal.h>
+
+#include "pppd-private.h"
 #include "tdb.h"
 #include "spinlock.h"
+#include "pathnames.h"
 
 #define TDB_MAGIC_FOOD "TDB file\n"
 #define TDB_VERSION (0x26011967 + 6)
@@ -1728,7 +1731,12 @@ TDB_CONTEXT *tdb_open_ex(const char *name, int hash_size, int tdb_flags,
                goto internal;
        }
 
+again:
        if ((tdb->fd = open(name, open_flags, mode)) == -1) {
+               if ((open_flags & O_CREAT) && errno == ENOENT &&
+                       mkdir_recursive(PPP_PATH_VARRUN) == 0)
+                       goto again;
+
                TDB_LOG((tdb, 5, "tdb_open_ex: could not open file %s: %s\n",
                         name, strerror(errno)));
                goto fail;      /* errno set by open(2) */
index c1bdbbbfe4e52d8a06edb3c8b22462580b3644a3..c47192e67fef1a853830bb3d3ffd6885d756abbd 100644 (file)
@@ -781,6 +781,88 @@ complete_read(int fd, void *buf, size_t count)
 }
 #endif
 
+/*
+ * mkdir_check - helper for mkdir_recursive, creates a directory
+ * but do not error on EEXIST if and only if entry is a directory
+ * The caller must check for errno == ENOENT if appropriate.
+ */
+static int
+mkdir_check(const char *path)
+{
+    struct stat statbuf;
+
+    if (mkdir(path, 0755) >= 0)
+       return 0;
+
+    if (errno == EEXIST) {
+       if (stat(path, &statbuf) < 0)
+           /* got raced? */
+           return -1;
+
+       if ((statbuf.st_mode & S_IFMT) == S_IFDIR)
+           return 0;
+
+       /* already exists but not a dir, treat as failure */
+       errno = EEXIST;
+       return -1;
+    }
+
+    return -1;
+}
+
+/*
+ * mkdir_parent - helper for mkdir_recursive, modifies the string in place
+ * Assumes mkdir(path) already failed, so it first creates the parent then
+ * full path again.
+ */
+static int
+mkdir_parent(char *path)
+{
+    char *slash;
+    int rc;
+
+    slash = strrchr(path, '/');
+    if (!slash)
+       return -1;
+
+    *slash = 0;
+    if (mkdir_check(path) < 0) {
+       if (errno != ENOENT) {
+           *slash = '/';
+           return -1;
+       }
+       if (mkdir_parent(path) < 0) {
+           *slash = '/';
+           return -1;
+       }
+    }
+    *slash = '/';
+
+    return mkdir_check(path);
+}
+
+/*
+ * mkdir_recursive - recursively create directory if it didn't exist
+ */
+int
+mkdir_recursive(const char *path)
+{
+    char *copy;
+    int rc;
+
+    // optimistically try on full path first to avoid allocation
+    if (mkdir_check(path) == 0)
+       return 0;
+
+    copy = strdup(path);
+    if (!copy)
+       return -1;
+
+    rc = mkdir_parent(copy);
+    free(copy);
+    return rc;
+}
+
 /* Procedures for locking the serial device using a lock file. */
 static char lock_file[MAXPATHLEN];
 
diff --git a/pppd/utils_utest.c b/pppd/utils_utest.c
new file mode 100644 (file)
index 0000000..cdca97e
--- /dev/null
@@ -0,0 +1,139 @@
+#include <fcntl.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "pppd-private.h"
+
+/* globals used in test.c... */
+int debug = 1;
+int error_count;
+int unsuccess;
+
+/* check if path exists and returns its type */
+static int
+file_type(char *path)
+{
+    struct stat statbuf;
+
+    if (stat(path, &statbuf) < 0)
+       return -1;
+
+    return statbuf.st_mode & S_IFMT;
+}
+
+int
+test_simple() {
+    if (mkdir_recursive("dir"))
+       return -1;
+
+    if (file_type("dir") != S_IFDIR)
+       return -1;
+
+    rmdir("dir");
+    return 0;
+}
+
+int
+test_recurse() {
+    if (mkdir_recursive("dir/subdir/subsubdir"))
+       return -1;
+
+    if (file_type("dir/subdir/subsubdir") != S_IFDIR)
+       return -1;
+
+    rmdir("dir/subdir/subsubdir");
+
+    /* try again with partial existence */
+    if (mkdir_recursive("dir/subdir/subsubdir"))
+       return -1;
+
+    if (file_type("dir/subdir/subsubdir") != S_IFDIR)
+       return -1;
+
+    rmdir("dir/subdir/subsubdir");
+    rmdir("dir/subdir");
+    rmdir("dir");
+    return 0;
+}
+
+int
+test_recurse_multislash() {
+    if (mkdir_recursive("dir/subdir///subsubdir"))
+       return -1;
+
+    if (file_type("dir/subdir/subsubdir") != S_IFDIR)
+       return -1;
+
+    rmdir("dir/subdir/subsubdir");
+    rmdir("dir/subdir");
+
+    /* try again with partial existence */
+    if (mkdir_recursive("dir/subdir/subsubdir///"))
+       return -1;
+
+    if (file_type("dir/subdir/subsubdir") != S_IFDIR)
+       return -1;
+
+    rmdir("dir/subdir/subsubdir");
+    rmdir("dir/subdir");
+    rmdir("dir");
+    return 0;
+}
+
+int
+test_parent_notdir() {
+    int fd = open("file", O_CREAT, 0600);
+    if (fd < 0)
+       return -1;
+    close(fd);
+
+    if (mkdir_recursive("file") == 0)
+       return -1;
+    if (mkdir_recursive("file/dir") == 0)
+       return -1;
+
+    unlink("file");
+    return 0;
+}
+
+int
+main()
+{
+    char *base_dir = strdup("/tmp/ppp_utils_utest.XXXXXX");
+    int failure = 0;
+
+    if (mkdtemp(base_dir) == NULL) {
+       printf("Could not create test directory, aborting\n");
+       return 1;
+    }
+
+    if (chdir(base_dir) < 0) {
+       printf("Could not enter newly created test dir, aborting\n");
+       return 1;
+    }
+
+    if (test_simple()) {
+       printf("Could not create simple directory\n");
+       failure++;
+    }
+
+    if (test_recurse()) {
+       printf("Could not create recursive directory\n");
+       failure++;
+    }
+
+    if (test_recurse_multislash()) {
+       printf("Could not create recursive directory with multiple slashes\n");
+       failure++;
+    }
+
+    if (test_parent_notdir()) {
+       printf("Creating over a file appeared to work?\n");
+       failure++;
+    }
+
+    rmdir(base_dir);
+    free(base_dir);
+    return failure;
+}
diff --git a/scripts/lcp_rtt_dump b/scripts/lcp_rtt_dump
new file mode 100755 (executable)
index 0000000..7898f08
--- /dev/null
@@ -0,0 +1,81 @@
+#!/usr/bin/perl
+# vim: shiftwidth=4 tabstop=4
+#
+# This program dumps to standard output the content of the file written
+# by pppd's lcp-rtt-file configuration option.
+#
+# Copyright (C) Marco d'Itri <md@linux.it>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+use v5.14;
+use warnings;
+use autodie;
+
+use POSIX qw(strftime);
+
+{
+       my $data = read_data($ARGV[0] || '/run/ppp-rtt.data');
+       die "The data file is invalid!\n" if not $data;
+       dump_data($data);
+}
+
+sub dump_data {
+       my ($s) = @_;
+
+       say "status:   $s->{status}";
+       say "interval: $s->{echo_interval}";
+       say "position: $s->{position}";
+       say 'elements: ' . scalar(@{ $s->{data} });
+       say '';
+
+       foreach (my $i= 0; $i < @{ $s->{data} }; $i++) {
+               my $date = strftime('%F %T', localtime($s->{data}->[$i]->[0]));
+               print "$i\t$date\t$s->{data}->[$i]->[1]\t$s->{data}->[$i]->[2]\n";
+       }
+}
+
+sub read_data {
+       my ($file) = @_;
+
+       my $data;
+       open(my $fh, '<', $file);
+       binmode($fh);
+       my $bytes_read;
+       do {
+               $bytes_read = sysread($fh, $data, 8192, length($data));
+       } while ($bytes_read == 8192);
+       close($fh);
+
+       my ($magic, $status, $position, $echo_interval, $rest)
+               = unpack('NNNN a*', $data);
+       return undef if $magic != 0x19450425;
+
+       # the position is relative to the C array, not to the logical entries
+       $position /= 2;
+
+       my @rawdata = unpack('(N C a3)*', $rest);
+       my @data;
+       while (my ($time, $loss, $rtt) = splice(@rawdata, 0, 3)) {
+               push(@data, [ $time, unpack('N', "\000$rtt"), $loss ]);
+       }
+
+       if (0) {
+       @data =
+               # skip any "empty" (null) entries
+               grep { $_->[0] }
+               # rearrange the list in chronological order
+               (@data[$position+1 .. $#data], @data[0 .. $position]);
+       }
+
+       return {
+               status                  => $status,
+               echo_interval   => $echo_interval,
+               position                => $position,
+               data                    => \@data,
+       };
+}
+
diff --git a/scripts/lcp_rtt_exporter b/scripts/lcp_rtt_exporter
new file mode 100644 (file)
index 0000000..6fab745
--- /dev/null
@@ -0,0 +1,108 @@
+#!/usr/bin/perl
+# vim: shiftwidth=4 tabstop=4
+#
+# This CGI program is a Prometheus exporter for pppd's lcp-rtt-file feature.
+#
+# Copyright (C) Marco d'Itri <md@linux.it>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+use v5.14;
+use warnings;
+use autodie;
+
+use List::Util qw(sum max min);
+
+{
+       my $data = read_data('/run/ppp-rtt.data');
+       my $stats = compute_statistics($data, 60);
+
+       my $s = metrics($stats);
+
+       print "Content-type: text/plain\n\n$s";
+       exit;
+}
+
+sub metrics {
+       my ($stats) = @_;
+
+       my $s = <<END;
+# TYPE lcp_rtt_status gauge
+# HELP LCP RTT status
+lcp_rtt_status $stats->{status}
+END
+       foreach (qw(average min max loss)) {
+               next if not exists $stats->{$_};
+               $s .= <<END;
+# TYPE lcp_rtt_$_ gauge
+# HELP LCP RTT $_
+lcp_rtt_$_ $stats->{$_}
+END
+       }
+
+       return $s;
+}
+
+sub compute_statistics {
+       my ($data, $length) = @_;
+
+       my $cutoff = time() - $length;
+       my @e = grep { $_->[0] >= $cutoff } @{ $data->{data} };
+       return { status => -1 } if not @e; # no data
+
+       my $average = (sum map { $_->[1] } @e) / scalar(@e);
+       my $min = min map { $_->[1] } @e;
+       my $max = max map { $_->[1] } @e;
+       my $loss = sum map { $_->[2] } @e;
+
+       return {
+               status  => $data->{status},
+               average => $average,
+               min             => $min,
+               max             => $max,
+               loss    => $loss,
+       };
+}
+
+sub read_data {
+       my ($file) = @_;
+
+       my $data;
+       open(my $fh, '<', $file);
+       binmode($fh);
+       my $bytes_read;
+       do {
+               $bytes_read = sysread($fh, $data, 8192, length($data));
+       } while ($bytes_read == 8192);
+       close($fh);
+
+       my ($magic, $status, $position, $echo_interval, $rest)
+               = unpack('NNNN a*', $data);
+       return undef if $magic != 0x19450425;
+
+       # the position is relative to the C array, not to the logical entries
+       $position /= 2;
+
+       my @rawdata = unpack('(N C a3)*', $rest);
+       my @data;
+       while (my ($time, $loss, $rtt) = splice(@rawdata, 0, 3)) {
+               push(@data, [ $time, unpack('N', "\000$rtt"), $loss ]);
+       }
+
+       @data =
+               # skip any "empty" (null) entries
+               grep { $_->[0] }
+               # rearrange the list in chronological order
+               (@data[$position+1 .. $#data], @data[0 .. $position]);
+
+       return {
+               status                  => $status,
+               echo_interval   => $echo_interval,
+               position                => $position,
+               data                    => \@data,
+       };
+}
+