diff options
Diffstat (limited to 'package/network/services/ppp/patches/141-Expand-byte-count-statistics-to-64-bits-298.patch')
| -rw-r--r-- | package/network/services/ppp/patches/141-Expand-byte-count-statistics-to-64-bits-298.patch | 518 |
1 files changed, 518 insertions, 0 deletions
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 index 00000000000..e4de5c0aa21 --- /dev/null +++ b/package/network/services/ppp/patches/141-Expand-byte-count-statistics-to-64-bits-298.patch @@ -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); + } + + /******************************************************************** |
