ppp: backport patches improving ppp interface creation
[openwrt/staging/stintel.git] / package / network / services / ppp / patches / 141-Expand-byte-count-statistics-to-64-bits-298.patch
diff --git a/package/network/services/ppp/patches/141-Expand-byte-count-statistics-to-64-bits-298.patch b/package/network/services/ppp/patches/141-Expand-byte-count-statistics-to-64-bits-298.patch
new file mode 100644 (file)
index 0000000..e4de5c0
--- /dev/null
@@ -0,0 +1,518 @@
+From 81ad945630120cc1c27c8bb00503be42b76ff202 Mon Sep 17 00:00:00 2001
+From: Jaco Kroon <jaco@uls.co.za>
+Date: Thu, 13 Jan 2022 08:38:04 +0200
+Subject: [PATCH] Expand byte count statistics to 64 bits (#298)
+
+* Add Gigawords to radius packets where applicable.
+
+IMPORTANT NOTE:  The ioctl() only supports 32-bit counters.  In order t
+obtain 64-bit counters, these are now pulled in from sysfs (it's assumed
+to be mounted on /sys which I'm assuming is standard).
+
+It is unknown whether sysfs will be available everywhere, as such, keep
+the ioctl() method in place, but attempt to detect wrap-overs.
+
+If the sysfs mechanism fails, fail back to the ioctl().
+
+Given maximum data rates, the intervals between calling this needs to be
+such that no more than 4GB (2^32) bytes are sent or received in any
+given interval.  Mostly important for radius plugin where data
+accounting may be in effect.
+
+Towards this, a timer interval on 25 seconds is set to force a ioctl()
+poll irrespective of the rate of stats update calls.  This may be
+important for especially radius that needs to provide interim-update
+intervals, if the interim updates is too long and the counters could
+wrap-over twice in a single interval.  At 25 seconds we should detect
+all wraps up to an effective data rate of 1.37Gbps, which for my
+purposes is adequate.
+
+Possible downsides, 4 files are opened, read and closed every time
+statistics is requested.  This results in 12 system calls every single
+time statistics is required, compared to 1 for the ioctl.  Efficiency is
+unknown, but as a rule of thumb fewer system calls are better, this is
+however not a critical path in my opinion, so should not be a problem.
+If required I can run a few benchmarks using gettimeofday() to measure
+actual impact.
+
+Signed-off-by: Jaco Kroon <jaco@uls.co.za>
+
+* Use netlink if possible to obtain 64-bit stats.
+
+This uses two system calls per round.
+
+This should be preferred where available.  It seems the RTM_GETSTATS was
+only added from 2016 some point (4.7.0 as per pali), which is in my
+opinion old, but given experience with certain embedded systems does
+need to be supported.
+
+Signed-off-by: Jaco Kroon <jaco@uls.co.za>
+
+Co-authored-by: Jaco Kroon <jaco@iewc.co.za>
+---
+ pppd/main.c                        |   5 +-
+ pppd/plugins/radius/etc/dictionary |   2 +
+ pppd/plugins/radius/radius.c       |  28 ++-
+ pppd/plugins/radius/radiusclient.h |   2 +
+ pppd/pppd.h                        |   9 +-
+ pppd/sys-linux.c                   | 281 ++++++++++++++++++++++++++++-
+ 6 files changed, 313 insertions(+), 14 deletions(-)
+
+--- a/pppd/main.c
++++ b/pppd/main.c
+@@ -87,6 +87,7 @@
+ #include <sys/socket.h>
+ #include <netinet/in.h>
+ #include <arpa/inet.h>
++#include <inttypes.h>
+ #include "pppd.h"
+ #include "magic.h"
+@@ -1230,9 +1231,9 @@ update_link_stats(int u)
+     slprintf(numbuf, sizeof(numbuf), "%u", link_connect_time);
+     script_setenv("CONNECT_TIME", numbuf, 0);
+-    slprintf(numbuf, sizeof(numbuf), "%u", link_stats.bytes_out);
++    snprintf(numbuf, sizeof(numbuf), "%" PRIu64, link_stats.bytes_out);
+     script_setenv("BYTES_SENT", numbuf, 0);
+-    slprintf(numbuf, sizeof(numbuf), "%u", link_stats.bytes_in);
++    snprintf(numbuf, sizeof(numbuf), "%" PRIu64, link_stats.bytes_in);
+     script_setenv("BYTES_RCVD", numbuf, 0);
+ }
+--- a/pppd/plugins/radius/etc/dictionary
++++ b/pppd/plugins/radius/etc/dictionary
+@@ -82,6 +82,8 @@ ATTRIBUTE    Acct-Session-Time       46      integer
+ ATTRIBUTE     Acct-Input-Packets      47      integer
+ ATTRIBUTE     Acct-Output-Packets     48      integer
+ ATTRIBUTE     Acct-Terminate-Cause    49      integer
++ATTRIBUTE     Acct-Input-Gigawords    52      integer
++ATTRIBUTE     Acct-Output-Gigawords   53      integer
+ ATTRIBUTE       Chap-Challenge          60      string
+ ATTRIBUTE     NAS-Port-Type           61      integer
+ ATTRIBUTE     Port-Limit              62      integer
+--- a/pppd/plugins/radius/radius.c
++++ b/pppd/plugins/radius/radius.c
+@@ -1020,12 +1020,22 @@ radius_acct_stop(void)
+       av_type = link_connect_time;
+       rc_avpair_add(&send, PW_ACCT_SESSION_TIME, &av_type, 0, VENDOR_NONE);
+-      av_type = link_stats.bytes_out;
++      av_type = link_stats.bytes_out & 0xFFFFFFFF;
+       rc_avpair_add(&send, PW_ACCT_OUTPUT_OCTETS, &av_type, 0, VENDOR_NONE);
+-      av_type = link_stats.bytes_in;
++      if (link_stats.bytes_out > 0xFFFFFFFF) {
++          av_type = link_stats.bytes_out >> 32;
++          rc_avpair_add(&send, PW_ACCT_OUTPUT_GIGAWORDS, &av_type, 0, VENDOR_NONE);
++      }
++
++      av_type = link_stats.bytes_in & 0xFFFFFFFF;
+       rc_avpair_add(&send, PW_ACCT_INPUT_OCTETS, &av_type, 0, VENDOR_NONE);
++      if (link_stats.bytes_in > 0xFFFFFFFF) {
++          av_type = link_stats.bytes_in >> 32;
++          rc_avpair_add(&send, PW_ACCT_INPUT_GIGAWORDS, &av_type, 0, VENDOR_NONE);
++      }
++
+       av_type = link_stats.pkts_out;
+       rc_avpair_add(&send, PW_ACCT_OUTPUT_PACKETS, &av_type, 0, VENDOR_NONE);
+@@ -1172,12 +1182,22 @@ radius_acct_interim(void *ignored)
+       av_type = link_connect_time;
+       rc_avpair_add(&send, PW_ACCT_SESSION_TIME, &av_type, 0, VENDOR_NONE);
+-      av_type = link_stats.bytes_out;
++      av_type = link_stats.bytes_out & 0xFFFFFFFF;
+       rc_avpair_add(&send, PW_ACCT_OUTPUT_OCTETS, &av_type, 0, VENDOR_NONE);
+-      av_type = link_stats.bytes_in;
++      if (link_stats.bytes_out > 0xFFFFFFFF) {
++          av_type = link_stats.bytes_out >> 32;
++          rc_avpair_add(&send, PW_ACCT_OUTPUT_GIGAWORDS, &av_type, 0, VENDOR_NONE);
++      }
++
++      av_type = link_stats.bytes_in & 0xFFFFFFFF;
+       rc_avpair_add(&send, PW_ACCT_INPUT_OCTETS, &av_type, 0, VENDOR_NONE);
++      if (link_stats.bytes_in > 0xFFFFFFFF) {
++          av_type = link_stats.bytes_in >> 32;
++          rc_avpair_add(&send, PW_ACCT_INPUT_GIGAWORDS, &av_type, 0, VENDOR_NONE);
++      }
++
+       av_type = link_stats.pkts_out;
+       rc_avpair_add(&send, PW_ACCT_OUTPUT_PACKETS, &av_type, 0, VENDOR_NONE);
+--- a/pppd/plugins/radius/radiusclient.h
++++ b/pppd/plugins/radius/radiusclient.h
+@@ -184,6 +184,8 @@ typedef struct pw_auth_hdr
+ #define PW_ACCT_LINK_COUNT            51      /* integer */
+ /* From RFC 2869 */
++#define PW_ACCT_INPUT_GIGAWORDS         52    /* integer */
++#define PW_ACCT_OUTPUT_GIGAWORDS        53    /* integer */
+ #define PW_ACCT_INTERIM_INTERVAL        85    /* integer */
+ /*    Merit Experimental Extensions */
+--- a/pppd/pppd.h
++++ b/pppd/pppd.h
+@@ -53,6 +53,7 @@
+ #include <stdlib.h>           /* for encrypt */
+ #include <unistd.h>           /* for setkey */
+ #include <stdarg.h>
++#include <stdint.h>
+ #include <limits.h>           /* for NGROUPS_MAX */
+ #include <sys/param.h>                /* for MAXPATHLEN and BSD4_4, if defined */
+ #include <sys/types.h>                /* for u_int32_t, if defined */
+@@ -173,8 +174,8 @@ struct permitted_ip {
+  * pppd needs.
+  */
+ struct pppd_stats {
+-    unsigned int      bytes_in;
+-    unsigned int      bytes_out;
++    uint64_t          bytes_in;
++    uint64_t          bytes_out;
+     unsigned int      pkts_in;
+     unsigned int      pkts_out;
+ };
+@@ -347,7 +348,7 @@ extern char *max_tls_version;
+ extern unsigned int maxoctets;             /* Maximum octetes per session (in bytes) */
+ extern int       maxoctets_dir;      /* Direction :
+                                     0 - in+out (default)
+-                                    1 - in 
++                                    1 - in
+                                     2 - out
+                                     3 - max(in,out) */
+ extern int       maxoctets_timeout;  /* Timeout for check of octets limit */
+@@ -356,7 +357,7 @@ extern int       maxoctets_timeout;  /*
+ #define PPP_OCTETS_DIRECTION_OUT        2
+ #define PPP_OCTETS_DIRECTION_MAXOVERAL  3
+ /* same as previos, but little different on RADIUS side */
+-#define PPP_OCTETS_DIRECTION_MAXSESSION 4     
++#define PPP_OCTETS_DIRECTION_MAXSESSION 4
+ #endif
+ #ifdef PPP_FILTER
+--- a/pppd/sys-linux.c
++++ b/pppd/sys-linux.c
+@@ -79,6 +79,7 @@
+ #include <sys/sysmacros.h>
+ #include <errno.h>
++#include <stddef.h>
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <syslog.h>
+@@ -92,6 +93,7 @@
+ #include <ctype.h>
+ #include <termios.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
+@@ -121,9 +123,19 @@
+ #include <linux/ppp_defs.h>
+ #include <linux/if_ppp.h>
+-#ifdef INET6
+ #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
++
++#ifdef INET6
+ #include <linux/if_addr.h>
+ /* glibc versions prior to 2.24 do not define SOL_NETLINK */
+ #ifndef SOL_NETLINK
+@@ -1407,11 +1419,17 @@ get_idle_time(int u, struct ppp_idle *ip
+ /********************************************************************
+  *
+- * 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));
+@@ -1426,7 +1444,262 @@ get_ppp_stats(int u, struct pppd_stats *
+     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);
+ }
+ /********************************************************************