#include <sys/sysmacros.h>
#include <errno.h>
+#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <syslog.h>
#include <fcntl.h>
#include <ctype.h>
#include <unistd.h>
+#include <limits.h>
/* This is in netdevice.h. However, this compile will fail miserably if
you attempt to include netdevice.h because it has so many references
#include <linux/ppp_defs.h>
#include <linux/if_ppp.h>
-#ifdef INET6
+#ifdef PPP_WITH_IPV6CP
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
+#include <linux/if_link.h>
+/* Attempt at retaining compile-support with older than 4.7 kernels, or kernels
+ * where RTM_NEWSTATS isn't defined for whatever reason.
+ */
+#ifndef RTM_NEWSTATS
+#define RTM_NEWSTATS 92
+#define RTM_GETSTATS 94
+#define IFLA_STATS_LINK_64 1
+#endif /* PPP_WITH_IPV6CP */
+
#include <linux/if_addr.h>
/* glibc versions prior to 2.24 do not define SOL_NETLINK */
#ifndef SOL_NETLINK
#define SOL_NETLINK 270
#endif
+
/* linux kernel versions prior to 4.3 do not define/support NETLINK_CAP_ACK */
#ifndef NETLINK_CAP_ACK
#define NETLINK_CAP_ACK 10
#include "fsm.h"
#include "ipcp.h"
-#ifdef IPX_CHANGE
-#include "ipxcp.h"
-#if __GLIBC__ >= 2 && \
- !(defined(__powerpc__) && __GLIBC__ == 2 && __GLIBC_MINOR__ == 0)
-#include <netipx/ipx.h>
-#else
-#include <linux/ipx.h>
-#endif
-#endif /* IPX_CHANGE */
-
-#ifdef PPP_FILTER
+#ifdef PPP_WITH_FILTER
#include <pcap-bpf.h>
#include <linux/filter.h>
-#endif /* PPP_FILTER */
+#endif /* PPP_WITH_FILTER */
#ifdef LOCKLIB
#include <sys/locks.h>
*/
#include "termios_linux.h"
-#ifdef INET6
+#ifdef PPP_WITH_IPV6CP
#ifndef _LINUX_IN6_H
/*
* This is in linux/include/net/ipv6.h.
} while (0)
static const eui64_t nulleui64;
-#endif /* INET6 */
+#endif /* PPP_WITH_IPV6CP */
/* We can get an EIO error on an ioctl if the modem has hung up */
#define ok_error(num) ((num)==EIO)
static int sock_fd = -1; /* socket for doing interface ioctls */
static int slave_fd = -1; /* pty for old-style demand mode, slave */
static int master_fd = -1; /* pty for old-style demand mode, master */
-#ifdef INET6
+#ifdef PPP_WITH_IPV6CP
static int sock6_fd = -1;
-#endif /* INET6 */
+#endif /* PPP_WITH_IPV6CP */
/*
* For the old-style kernel driver, this is the same as ppp_fd.
if (sock_fd < 0)
fatal("Couldn't create IP socket: %m(%d)", errno);
-#ifdef INET6
+#ifdef PPP_WITH_IPV6CP
sock6_fd = socket(AF_INET6, SOCK_DGRAM, 0);
if (sock6_fd < 0)
sock6_fd = -errno; /* save errno for later */
if_is_up = 0;
sifdown(0);
}
+#ifdef PPP_WITH_IPV6CP
if (if6_is_up)
sif6down(0);
+#endif
/*
* Delete any routes through the device.
*/
if (have_default_route)
cifdefaultroute(0, 0, 0);
-#ifdef INET6
+#ifdef PPP_WITH_IPV6CP
if (have_default_route6)
cif6defaultroute(0, nulleui64, nulleui64);
#endif
close(ppp_dev_fd);
if (sock_fd >= 0)
close(sock_fd);
-#ifdef INET6
+#ifdef PPP_WITH_IPV6CP
if (sock6_fd >= 0)
close(sock6_fd);
#endif
#ifdef B115200
{ 115200, B115200 },
#endif
+#ifdef B153600
+ { 153600, B153600 },
+#endif
#ifdef EXTA
{ 19200, EXTA },
#endif
#ifdef B230400
{ 230400, B230400 },
#endif
+#ifdef B307200
+ { 307200, B307200 },
+#endif
#ifdef B460800
{ 460800, B460800 },
#endif
#ifdef B576000
{ 576000, B576000 },
#endif
+#ifdef B614400
+ { 614400, B614400 },
+#endif
#ifdef B921600
{ 921600, B921600 },
#endif
modify_flags(ppp_dev_fd, SC_CCP_OPEN|SC_CCP_UP, x);
}
-#ifdef PPP_FILTER
+#ifdef PPP_WITH_FILTER
/*
* set_filters - set the active and pass filters in the kernel driver.
*/
}
return 1;
}
-#endif /* PPP_FILTER */
+#endif /* PPP_WITH_FILTER */
/********************************************************************
*
/********************************************************************
*
- * get_ppp_stats - return statistics for the link.
+ * get_ppp_stats_iocl - return statistics for the link, using the ioctl() method,
+ * this only supports 32-bit counters, so need to count the wraps.
*/
-int
-get_ppp_stats(int u, struct pppd_stats *stats)
+static int
+get_ppp_stats_ioctl(int u, struct pppd_stats *stats)
{
+ static u_int32_t previbytes = 0;
+ static u_int32_t prevobytes = 0;
+ static u_int32_t iwraps = 0;
+ static u_int32_t owraps = 0;
+
struct ifpppstatsreq req;
memset (&req, 0, sizeof (req));
stats->bytes_out = req.stats.p.ppp_obytes;
stats->pkts_in = req.stats.p.ppp_ipackets;
stats->pkts_out = req.stats.p.ppp_opackets;
+
+ if (stats->bytes_in < previbytes)
+ ++iwraps;
+ if (stats->bytes_out < prevobytes)
+ ++owraps;
+
+ previbytes = stats->bytes_in;
+ prevobytes = stats->bytes_out;
+
+ stats->bytes_in += (uint64_t)iwraps << 32;
+ stats->bytes_out += (uint64_t)owraps << 32;
+
+ return 1;
+}
+
+/********************************************************************
+ * get_ppp_stats_rtnetlink - return statistics for the link, using rtnetlink
+ * This provides native 64-bit counters.
+ */
+static int
+get_ppp_stats_rtnetlink(int u, struct pppd_stats *stats)
+{
+ static int rtnl_fd = -1;
+
+ struct sockaddr_nl nladdr;
+ struct {
+ struct nlmsghdr nlh;
+ struct if_stats_msg ifsm;
+ } nlreq;
+ struct nlresp {
+ struct nlmsghdr nlh;
+ union {
+ struct {
+ struct nlmsgerr nlerr;
+ char __end_err[0];
+ };
+ struct {
+ struct rtmsg rth;
+ struct {
+ /* We only case about these first fields from rtnl_link_stats64 */
+ uint64_t rx_packets;
+ uint64_t tx_packets;
+ uint64_t rx_bytes;
+ uint64_t tx_bytes;
+ } stats;
+ char __end_stats[0];
+ };
+ };
+ } nlresp;
+ ssize_t nlresplen;
+ struct iovec iov;
+ struct msghdr msg;
+
+ memset(&nladdr, 0, sizeof(nladdr));
+ nladdr.nl_family = AF_NETLINK;
+
+ if (rtnl_fd < 0) {
+ rtnl_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+ if (rtnl_fd < 0) {
+ error("get_ppp_stats_rtnetlink: error creating NETLINK socket: %m (line %d)", __LINE__);
+ return 0;
+ }
+
+ if (bind(rtnl_fd, (struct sockaddr *)&nladdr, sizeof(nladdr)) < 0) {
+ error("get_ppp_stats_rtnetlink: bind(AF_NETLINK): %m (line %d)", __LINE__);
+ goto err;
+ }
+ }
+
+ memset(&nlreq, 0, sizeof(nlreq));
+ nlreq.nlh.nlmsg_len = sizeof(nlreq);
+ nlreq.nlh.nlmsg_type = RTM_GETSTATS;
+ nlreq.nlh.nlmsg_flags = NLM_F_REQUEST;
+
+ nlreq.ifsm.ifindex = if_nametoindex(ifname);
+ nlreq.ifsm.filter_mask = IFLA_STATS_LINK_64;
+
+ memset(&iov, 0, sizeof(iov));
+ iov.iov_base = &nlreq;
+ iov.iov_len = sizeof(nlreq);
+
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_name = &nladdr;
+ msg.msg_namelen = sizeof(nladdr);
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+
+ if (sendmsg(rtnl_fd, &msg, 0) < 0) {
+ error("get_ppp_stats_rtnetlink: sendmsg(RTM_GETSTATS): %m (line %d)", __LINE__);
+ goto err;
+ }
+
+ /* We just need to repoint to IOV ... everything else stays the same */
+ iov.iov_base = &nlresp;
+ iov.iov_len = sizeof(nlresp);
+
+ nlresplen = recvmsg(rtnl_fd, &msg, 0);
+
+ if (nlresplen < 0) {
+ error("get_ppp_stats_rtnetlink: recvmsg(RTM_GETSTATS): %m (line %d)", __LINE__);
+ goto err;
+ }
+
+ if (nlresplen < sizeof(nlresp.nlh)) {
+ error("get_ppp_stats_rtnetlink: Netlink response message was incomplete (line %d)", __LINE__);
+ goto err;
+ }
+
+ if (nlresp.nlh.nlmsg_type == NLMSG_ERROR) {
+ if (nlresplen < offsetof(struct nlresp, __end_err)) {
+ if (kernel_version >= KVERSION(4,7,0))
+ error("get_ppp_stats_rtnetlink: Netlink responded with error: %s (line %d)", strerror(-nlresp.nlerr.error), __LINE__);
+ } else {
+ error("get_ppp_stats_rtnetlink: Netlink responded with an error message, but the nlmsgerr structure is incomplete (line %d).",
+ __LINE__);
+ }
+ goto err;
+ }
+
+ if (nlresp.nlh.nlmsg_type != RTM_NEWSTATS) {
+ error("get_ppp_stats_rtnetlink: Expected RTM_NEWSTATS response, found something else (mlmsg_type %d, line %d)",
+ nlresp.nlh.nlmsg_type, __LINE__);
+ goto err;
+ }
+
+ if (nlresplen < offsetof(struct nlresp, __end_stats)) {
+ error("get_ppp_stats_rtnetlink: Obtained an insufficiently sized rtnl_link_stats64 struct from the kernel (line %d).", __LINE__);
+ goto err;
+ }
+
+ stats->bytes_in = nlresp.stats.rx_bytes;
+ stats->bytes_out = nlresp.stats.tx_bytes;
+ stats->pkts_in = nlresp.stats.rx_packets;
+ stats->pkts_out = nlresp.stats.tx_packets;
+
return 1;
+err:
+ close(rtnl_fd);
+ rtnl_fd = -1;
+ return 0;
+}
+
+/********************************************************************
+ * get_ppp_stats_sysfs - return statistics for the link, using the files in sysfs,
+ * this provides native 64-bit counters.
+ */
+static int
+get_ppp_stats_sysfs(int u, struct pppd_stats *stats)
+{
+ char fname[PATH_MAX+1];
+ char buf[21], *err; /* 2^64 < 10^20 */
+ int blen, fd, rlen;
+ unsigned long long val;
+
+ struct {
+ const char* fname;
+ void* ptr;
+ unsigned size;
+ } slist[] = {
+#define statfield(fn, field) { .fname = #fn, .ptr = &stats->field, .size = sizeof(stats->field) }
+ statfield(rx_bytes, bytes_in),
+ statfield(tx_bytes, bytes_out),
+ statfield(rx_packets, pkts_in),
+ statfield(tx_packets, pkts_out),
+#undef statfield
+ };
+
+ blen = snprintf(fname, sizeof(fname), "/sys/class/net/%s/statistics/", ifname);
+ if (blen >= sizeof(fname))
+ return 0; /* ifname max 15, so this should be impossible */
+
+ for (int i = 0; i < sizeof(slist) / sizeof(*slist); ++i) {
+ if (snprintf(fname + blen, sizeof(fname) - blen, "%s", slist[i].fname) >= sizeof(fname) - blen) {
+ fname[blen] = 0;
+ error("sysfs stats: filename %s/%s overflowed PATH_MAX", fname, slist[i].fname);
+ return 0;
+ }
+
+ fd = open(fname, O_RDONLY);
+ if (fd < 0) {
+ error("%s: %m", fname);
+ return 0;
+ }
+
+ rlen = read(fd, buf, sizeof(buf) - 1);
+ close(fd);
+ if (rlen < 0) {
+ error("%s: %m", fname);
+ return 0;
+ }
+ /* trim trailing \n if present */
+ while (rlen > 0 && buf[rlen-1] == '\n')
+ rlen--;
+ buf[rlen] = 0;
+
+ errno = 0;
+ val = strtoull(buf, &err, 10);
+ if (*buf < '0' || *buf > '9' || errno != 0 || *err) {
+ error("string to number conversion error converting %s (from %s) for remaining string %s%s%s",
+ buf, fname, err, errno ? ": " : "", errno ? strerror(errno) : "");
+ return 0;
+ }
+ switch (slist[i].size) {
+#define stattype(type) case sizeof(type): *(type*)slist[i].ptr = (type)val; break
+ stattype(uint64_t);
+ stattype(uint32_t);
+ stattype(uint16_t);
+ stattype(uint8_t);
+#undef stattype
+ default:
+ error("Don't know how to store stats for %s of size %u", slist[i].fname, slist[i].size);
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+/********************************************************************
+ * Periodic timer function to be used to keep stats up to date in case of ioctl
+ * polling.
+ *
+ * Given the 25s interval this should be fine up to data rates of 1.37Gbps.
+ * If you do change the timer, remember to also bring the get_ppp_stats (which
+ * sets up the initial trigger) as well.
+ */
+static void
+ppp_stats_poller(void* u)
+{
+ struct pppd_stats dummy;
+ get_ppp_stats_ioctl((long)u, &dummy);
+ TIMEOUT(ppp_stats_poller, u, 25);
+}
+
+/********************************************************************
+ * get_ppp_stats - return statistics for the link.
+ */
+int get_ppp_stats(int u, struct pppd_stats *stats)
+{
+ static int (*func)(int, struct pppd_stats*) = NULL;
+
+ if (!func) {
+ if (get_ppp_stats_rtnetlink(u, stats)) {
+ func = get_ppp_stats_rtnetlink;
+ return 1;
+ }
+ if (get_ppp_stats_sysfs(u, stats)) {
+ func = get_ppp_stats_sysfs;
+ return 1;
+ }
+ warn("statistics falling back to ioctl which only supports 32-bit counters");
+ func = get_ppp_stats_ioctl;
+ TIMEOUT(ppp_stats_poller, (void*)(long)u, 25);
+ }
+
+ return func(u, stats);
}
/********************************************************************
return 1;
}
-#ifdef INET6
+#ifdef PPP_WITH_IPV6CP
/*
* /proc/net/ipv6_route parsing stuff.
*/
return 1;
}
-#endif /* INET6 */
+#endif /* PPP_WITH_IPV6CP */
/********************************************************************
*
if (if_is_up && --if_is_up > 0)
return 1;
-#ifdef INET6
+#ifdef PPP_WITH_IPV6CP
if (if6_is_up)
return 1;
-#endif /* INET6 */
+#endif /* PPP_WITH_IPV6CP */
return setifstate(u, 0);
}
-#ifdef INET6
+#ifdef PPP_WITH_IPV6CP
/********************************************************************
*
* sif6up - Config the interface up for IPv6
return setifstate(u, 0);
}
-#endif /* INET6 */
+#endif /* PPP_WITH_IPV6CP */
/********************************************************************
*
}
}
- /* This way it is possible to have an IPX-only or IPv6-only interface */
+ /* This way it is possible to have an IPv6-only interface */
memset(&ifr, 0, sizeof(ifr));
SET_SA_FAMILY(ifr.ifr_addr, AF_INET);
strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
return 1;
}
-#ifdef INET6
+#ifdef PPP_WITH_IPV6CP
/********************************************************************
*
* sif6addr_rtnetlink - Config the interface with both IPv6 link-local addresses via rtnetlink
}
return 1;
}
-#endif /* INET6 */
+#endif /* PPP_WITH_IPV6CP */
/*
* get_pty - get a pty master/slave pair and chown the slave side
return 1;
}
-\f
-/********************************************************************
- *
- * sipxfaddr - Config the interface IPX networknumber
- */
-
-int sipxfaddr (int unit, unsigned long int network, unsigned char * node )
-{
- int result = 1;
-
-#ifdef IPX_CHANGE
- int skfd;
- struct ifreq ifr;
- struct sockaddr_ipx *sipx = (struct sockaddr_ipx *) &ifr.ifr_addr;
-
- skfd = socket (AF_IPX, SOCK_DGRAM, 0);
- if (skfd < 0) {
- if (! ok_error (errno))
- dbglog("socket(AF_IPX): %m (line %d)", __LINE__);
- result = 0;
- }
- else {
- memset (&ifr, '\0', sizeof (ifr));
- strlcpy (ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
-
- memcpy (sipx->sipx_node, node, IPX_NODE_LEN);
- sipx->sipx_family = AF_IPX;
- sipx->sipx_port = 0;
- sipx->sipx_network = htonl (network);
- sipx->sipx_type = IPX_FRAME_ETHERII;
- sipx->sipx_action = IPX_CRTITF;
-/*
- * Set the IPX device
- */
- if (ioctl(skfd, SIOCSIFADDR, (caddr_t) &ifr) < 0) {
- result = 0;
- if (errno != EEXIST) {
- if (! ok_error (errno))
- dbglog("ioctl(SIOCSIFADDR, CRTITF): %m (line %d)", __LINE__);
- }
- else {
- warn("ioctl(SIOCSIFADDR, CRTITF): Address already exists");
- }
- }
- close (skfd);
- }
-#endif
- return result;
-}
-
-/********************************************************************
- *
- * cipxfaddr - Clear the information for the IPX network. The IPX routes
- * are removed and the device is no longer able to pass IPX
- * frames.
- */
-
-int cipxfaddr (int unit)
-{
- int result = 1;
-
-#ifdef IPX_CHANGE
- int skfd;
- struct ifreq ifr;
- struct sockaddr_ipx *sipx = (struct sockaddr_ipx *) &ifr.ifr_addr;
-
- skfd = socket (AF_IPX, SOCK_DGRAM, 0);
- if (skfd < 0) {
- if (! ok_error (errno))
- dbglog("socket(AF_IPX): %m (line %d)", __LINE__);
- result = 0;
- }
- else {
- memset (&ifr, '\0', sizeof (ifr));
- strlcpy (ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
-
- sipx->sipx_type = IPX_FRAME_ETHERII;
- sipx->sipx_action = IPX_DLTITF;
- sipx->sipx_family = AF_IPX;
-/*
- * Set the IPX device
- */
- if (ioctl(skfd, SIOCSIFADDR, (caddr_t) &ifr) < 0) {
- if (! ok_error (errno))
- info("ioctl(SIOCSIFADDR, IPX_DLTITF): %m (line %d)", __LINE__);
- result = 0;
- }
- close (skfd);
- }
-#endif
- return result;
-}
-
/*
* Use the hostname as part of the random number seed.
*/
int
sys_check_options(void)
{
-#ifdef IPX_CHANGE
-/*
- * Disable the IPX protocol if the support is not present in the kernel.
- */
- char *path;
-
- if (ipxcp_protent.enabled_flag) {
- struct stat stat_buf;
- if ( ((path = path_to_procfs("/net/ipx/interface")) == NULL
- && (path = path_to_procfs("/net/ipx_interface")) == NULL)
- || lstat(path, &stat_buf) < 0) {
- error("IPX support is not present in the kernel\n");
- ipxcp_protent.enabled_flag = 0;
- }
- }
-#endif
if (demand && driver_is_old) {
option_error("demand dialling is not supported by kernel driver "
"version %d.%d.%d", driver_version, driver_modification,