Implement net-init, net-pre-up and net-down scripts in pppd.
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
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 =
#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 */
"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,
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 */
}
}
+/*
+ * 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
*/
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;
}
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.
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
*/
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)
UNTIMEOUT (LcpEchoTimeout, f);
lcp_echo_timer_running = 0;
}
+
+ /* Close the file containing the LCP RTT data */
+ lcp_rtt_close_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
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 */
#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) {
\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)
.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
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>
.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
.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
.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
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>
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 */
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
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.
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
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
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
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
.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
#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)
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) */
}
#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];
--- /dev/null
+#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;
+}
--- /dev/null
+#!/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,
+ };
+}
+
--- /dev/null
+#!/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,
+ };
+}
+