X-Git-Url: http://git.ozlabs.org/?a=blobdiff_plain;f=pppd%2Fsys-linux.c;h=7718151c1882f4b6f6010095fc97f2b95f5ec2cf;hb=81a4d35394096e4e9bd7b9b189e91190e58b02a7;hp=e1bb8108ea0964d81d0c4b64ead2132e97aa5889;hpb=d2858ab22a536c8e58f7796b2947f5ff72bb4450;p=ppp.git diff --git a/pppd/sys-linux.c b/pppd/sys-linux.c index e1bb810..7718151 100644 --- a/pppd/sys-linux.c +++ b/pppd/sys-linux.c @@ -83,6 +83,7 @@ #include #include +#include #include #include #include @@ -96,8 +97,8 @@ #include #include #include -#include #include +#include /* This is in netdevice.h. However, this compile will fail miserably if you attempt to include netdevice.h because it has so many references @@ -110,7 +111,6 @@ #if !defined(__GLIBC__) || __GLIBC__ >= 2 #include /* glibc 2 conflicts with linux/types.h */ -#include #include #include #include @@ -128,9 +128,19 @@ #include #include -#ifdef INET6 #include #include +#include +/* 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 /* glibc versions prior to 2.24 do not define SOL_NETLINK */ #ifndef SOL_NETLINK @@ -165,19 +175,11 @@ #include #endif -#ifndef BOTHER -#define BOTHER 0010000 -#endif -struct termios2 { - unsigned int c_iflag; - unsigned int c_oflag; - unsigned int c_cflag; - unsigned int c_lflag; - unsigned char c_line; - unsigned char c_cc[19]; - unsigned int c_ispeed; - unsigned int c_ospeed; -}; +/* + * Instead of system header file use local "termios_linux.h" header + * file as it provides additional support for arbitrary baud rates via BOTHER. + */ +#include "termios_linux.h" #ifdef INET6 #ifndef _LINUX_IN6_H @@ -907,6 +909,9 @@ struct speed { #ifdef B115200 { 115200, B115200 }, #endif +#ifdef B153600 + { 153600, B153600 }, +#endif #ifdef EXTA { 19200, EXTA }, #endif @@ -916,6 +921,9 @@ struct speed { #ifdef B230400 { 230400, B230400 }, #endif +#ifdef B307200 + { 307200, B307200 }, +#endif #ifdef B460800 { 460800, B460800 }, #endif @@ -925,6 +933,9 @@ struct speed { #ifdef B576000 { 576000, B576000 }, #endif +#ifdef B614400 + { 614400, B614400 }, +#endif #ifdef B921600 { 921600, B921600 }, #endif @@ -1053,40 +1064,32 @@ void set_up_tty(int tty_fd, int local) cfsetospeed (&tios, speed); cfsetispeed (&tios, speed); speed = cfgetospeed(&tios); + baud_rate = baud_rate_of(speed); + } else { +#ifdef BOTHER + tios.c_cflag &= ~CBAUD; + tios.c_cflag |= BOTHER; + tios.c_ospeed = inspeed; +#ifdef IBSHIFT + /* B0 sets input baudrate to the output baudrate */ + tios.c_cflag &= ~(CBAUD << IBSHIFT); + tios.c_cflag |= B0 << IBSHIFT; + tios.c_ispeed = inspeed; +#endif + baud_rate = inspeed; +#else + baud_rate = 0; +#endif } - baud_rate = baud_rate_of(speed); } else { speed = cfgetospeed(&tios); baud_rate = baud_rate_of(speed); - } - - while (tcsetattr(tty_fd, TCSAFLUSH, &tios) < 0 && !ok_error(errno)) - if (errno != EINTR) - fatal("tcsetattr: %m (line %d)", __LINE__); - restore_term = 1; - -/* Most Linux architectures and drivers support arbitrary baud rate values via BOTHER */ -#ifdef TCGETS2 - if (!baud_rate) { - struct termios2 tios2; - if (ioctl(tty_fd, TCGETS2, &tios2) == 0) { - if (inspeed) { - tios2.c_cflag &= ~CBAUD; - tios2.c_cflag |= BOTHER; - tios2.c_ispeed = inspeed; - tios2.c_ospeed = inspeed; -#ifdef TCSETS2 - if (ioctl(tty_fd, TCSETS2, &tios2) == 0) - baud_rate = inspeed; +#ifdef BOTHER + if (!baud_rate) + baud_rate = tios.c_ospeed; #endif - } else { - if ((tios2.c_cflag & CBAUD) == BOTHER && tios2.c_ospeed) - baud_rate = tios2.c_ospeed; - } - } } -#endif /* * We can't proceed if the serial port baud rate is unknown, @@ -1098,6 +1101,11 @@ void set_up_tty(int tty_fd, int local) else fatal("Baud rate for %s is 0; need explicit baud rate", devnam); } + + while (tcsetattr(tty_fd, TCSAFLUSH, &tios) < 0 && !ok_error(errno)) + if (errno != EINTR) + fatal("tcsetattr: %m (line %d)", __LINE__); + restore_term = 1; } /******************************************************************** @@ -1463,11 +1471,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)); @@ -1482,9 +1496,264 @@ get_ppp_stats(int u, struct pppd_stats *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); +} + /******************************************************************** * * ccp_fatal_error - returns 1 if decompression was disabled as a @@ -2954,7 +3223,19 @@ static int sif6addr_rtnetlink(unsigned int iface, eui64_t our_eui64, eui64_t his IN6_LLADDR_FROM_EUI64(nlreq.addrs[0].addr, our_eui64); nlreq.addrs[1].rta.rta_len = sizeof(nlreq.addrs[1]); nlreq.addrs[1].rta.rta_type = IFA_ADDRESS; - IN6_LLADDR_FROM_EUI64(nlreq.addrs[1].addr, his_eui64); + + /* + * To set only local address, older kernel expects that local address is + * in IFA_ADDRESS field (not IFA_LOCAL). New kernels with support for peer + * address, ignore IFA_ADDRESS if is same as IFA_LOCAL. So for backward + * compatibility when setting only local address, set it via both IFA_LOCAL + * and IFA_ADDRESS fields. Same logic is implemented in 'ip address' command + * from iproute2 project. + */ + if (!eui64_iszero(his_eui64)) + IN6_LLADDR_FROM_EUI64(nlreq.addrs[1].addr, his_eui64); + else + IN6_LLADDR_FROM_EUI64(nlreq.addrs[1].addr, our_eui64); memset(&nladdr, 0, sizeof(nladdr)); nladdr.nl_family = AF_NETLINK; @@ -3072,7 +3353,9 @@ int sif6addr (int unit, eui64_t our_eui64, eui64_t his_eui64) error("sif6addr: ioctl(SIOCSIFADDR): %m (line %d)", __LINE__); return 0; } + } + if (!ret && !eui64_iszero(his_eui64)) { /* * Linux kernel does not provide AF_INET6 ioctl SIOCSIFDSTADDR for * setting remote peer host address, so set only route to remote host. @@ -3143,7 +3426,7 @@ int cif6addr (int unit, eui64_t our_eui64, eui64_t his_eui64) int get_pty(int *master_fdp, int *slave_fdp, char *slave_name, int uid) { - int i, mfd, sfd = -1; + int i, mfd, ret, sfd = -1; char pty_name[16]; struct termios tios; @@ -3181,8 +3464,14 @@ get_pty(int *master_fdp, int *slave_fdp, char *slave_name, int uid) pty_name[5] = 't'; sfd = open(pty_name, O_RDWR | O_NOCTTY, 0); if (sfd >= 0) { - fchown(sfd, uid, -1); - fchmod(sfd, S_IRUSR | S_IWUSR); + ret = fchown(sfd, uid, -1); + if (ret != 0) { + warn("Couldn't change ownership of %s, %m", pty_name); + } + ret = fchmod(sfd, S_IRUSR | S_IWUSR); + if (ret != 0) { + warn("Couldn't change permissions of %s, %m", pty_name); + } break; } close(mfd);