X-Git-Url: https://git.ozlabs.org/?p=ppp.git;a=blobdiff_plain;f=pppd%2Fsys-linux.c;h=6eed86f3f2a2b0bd6f1754375d1c6734380ce6a2;hp=b7972b908e1c972797f0c66fd4685cf9070ee45d;hb=4a54e34cf5629f9fed61f0b7d69ee3ba4d874bc6;hpb=952cfa5acc41ad4ceee160420a188a388bb340cf diff --git a/pppd/sys-linux.c b/pppd/sys-linux.c index b7972b9..6eed86f 100644 --- a/pppd/sys-linux.c +++ b/pppd/sys-linux.c @@ -69,6 +69,10 @@ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + #include #include #include @@ -79,19 +83,22 @@ #include #include +#include #include #include #include #include #include #include +#ifdef HAVE_UTMP_H #include +#endif #include #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 @@ -121,20 +128,43 @@ #include #include +#include +#include +#include + +#ifdef INET6 +#include +#endif + +/* 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 + +/* 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 +#endif + +/* linux kernel versions prior to 4.7 do not define/support IFLA_PPP_DEV_FD */ +#ifndef IFLA_PPP_MAX +/* IFLA_PPP_DEV_FD is declared as enum when IFLA_PPP_MAX is defined */ +#define IFLA_PPP_DEV_FD 1 +#endif + #include "pppd.h" #include "fsm.h" #include "ipcp.h" -#ifdef IPX_CHANGE -#include "ipxcp.h" -#if __GLIBC__ >= 2 && \ - !(defined(__powerpc__) && __GLIBC__ == 2 && __GLIBC_MINOR__ == 0) -#include -#else -#include -#endif -#endif /* IPX_CHANGE */ - #ifdef PPP_FILTER #include #include @@ -144,6 +174,12 @@ #include #endif +/* + * 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 /* @@ -158,9 +194,9 @@ struct in6_ifreq { #endif #define IN6_LLADDR_FROM_EUI64(sin6, eui64) do { \ - memset(&sin6.s6_addr, 0, sizeof(struct in6_addr)); \ - sin6.s6_addr16[0] = htons(0xfe80); \ - eui64_copy(eui64, sin6.s6_addr32[2]); \ + memset(&(sin6).s6_addr, 0, sizeof(struct in6_addr)); \ + (sin6).s6_addr16[0] = htons(0xfe80); \ + eui64_copy(eui64, (sin6).s6_addr32[2]); \ } while (0) static const eui64_t nulleui64; @@ -630,6 +666,160 @@ void generic_disestablish_ppp(int dev_fd) } } +/* + * make_ppp_unit_rtnetlink - register a new ppp network interface for ppp_dev_fd + * with specified req_ifname via rtnetlink. Interface name req_ifname must not + * be empty. Custom ppp unit id req_unit is ignored and kernel choose some free. + */ +static int make_ppp_unit_rtnetlink(void) +{ + struct { + struct nlmsghdr nlh; + struct ifinfomsg ifm; + struct { + struct rtattr rta; + char ifname[IFNAMSIZ]; + } ifn; + struct { + struct rtattr rta; + struct { + struct rtattr rta; + char ifkind[sizeof("ppp")]; + } ifik; + struct { + struct rtattr rta; + struct { + struct rtattr rta; + union { + int ppp_dev_fd; + } ppp; + } ifdata[1]; + } ifid; + } ifli; + } nlreq; + struct { + struct nlmsghdr nlh; + struct nlmsgerr nlerr; + } nlresp; + struct sockaddr_nl nladdr; + struct iovec iov; + struct msghdr msg; + ssize_t nlresplen; + int one; + int fd; + + fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (fd < 0) { + error("make_ppp_unit_rtnetlink: socket(NETLINK_ROUTE): %m (line %d)", __LINE__); + return 0; + } + + /* Tell kernel to not send to us payload of acknowledgment error message. */ + one = 1; + setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, &one, sizeof(one)); + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + + if (bind(fd, (struct sockaddr *)&nladdr, sizeof(nladdr)) < 0) { + error("make_ppp_unit_rtnetlink: bind(AF_NETLINK): %m (line %d)", __LINE__); + close(fd); + return 0; + } + + memset(&nlreq, 0, sizeof(nlreq)); + nlreq.nlh.nlmsg_len = sizeof(nlreq); + nlreq.nlh.nlmsg_type = RTM_NEWLINK; + nlreq.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE; + nlreq.ifm.ifi_family = AF_UNSPEC; + nlreq.ifm.ifi_type = ARPHRD_NETROM; + nlreq.ifn.rta.rta_len = sizeof(nlreq.ifn); + nlreq.ifn.rta.rta_type = IFLA_IFNAME; + strlcpy(nlreq.ifn.ifname, req_ifname, sizeof(nlreq.ifn.ifname)); + nlreq.ifli.rta.rta_len = sizeof(nlreq.ifli); + nlreq.ifli.rta.rta_type = IFLA_LINKINFO; + nlreq.ifli.ifik.rta.rta_len = sizeof(nlreq.ifli.ifik); + nlreq.ifli.ifik.rta.rta_type = IFLA_INFO_KIND; + strcpy(nlreq.ifli.ifik.ifkind, "ppp"); + nlreq.ifli.ifid.rta.rta_len = sizeof(nlreq.ifli.ifid); + nlreq.ifli.ifid.rta.rta_type = IFLA_INFO_DATA; + nlreq.ifli.ifid.ifdata[0].rta.rta_len = sizeof(nlreq.ifli.ifid.ifdata[0]); + nlreq.ifli.ifid.ifdata[0].rta.rta_type = IFLA_PPP_DEV_FD; + nlreq.ifli.ifid.ifdata[0].ppp.ppp_dev_fd = ppp_dev_fd; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + + 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(fd, &msg, 0) < 0) { + error("make_ppp_unit_rtnetlink: sendmsg(RTM_NEWLINK/NLM_F_CREATE): %m (line %d)", __LINE__); + close(fd); + return 0; + } + + memset(&iov, 0, sizeof(iov)); + iov.iov_base = &nlresp; + iov.iov_len = sizeof(nlresp); + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = &nladdr; + msg.msg_namelen = sizeof(nladdr); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + nlresplen = recvmsg(fd, &msg, 0); + + if (nlresplen < 0) { + error("make_ppp_unit_rtnetlink: recvmsg(NLM_F_ACK): %m (line %d)", __LINE__); + close(fd); + return 0; + } + + close(fd); + + if (nladdr.nl_family != AF_NETLINK) { + error("make_ppp_unit_rtnetlink: recvmsg(NLM_F_ACK): Not a netlink packet (line %d)", __LINE__); + return 0; + } + + if ((size_t)nlresplen < sizeof(nlresp) || nlresp.nlh.nlmsg_len < sizeof(nlresp)) { + error("make_ppp_unit_rtnetlink: recvmsg(NLM_F_ACK): Acknowledgment netlink packet too short (line %d)", __LINE__); + return 0; + } + + /* acknowledgment packet for NLM_F_ACK is NLMSG_ERROR */ + if (nlresp.nlh.nlmsg_type != NLMSG_ERROR) { + error("make_ppp_unit_rtnetlink: recvmsg(NLM_F_ACK): Not an acknowledgment netlink packet (line %d)", __LINE__); + return 0; + } + + /* error == 0 indicates success, negative value is errno code */ + if (nlresp.nlerr.error != 0) { + /* + * Linux kernel versions prior to 4.7 do not support creating ppp + * interfaces via rtnetlink API and therefore error response is + * expected. On older kernel versions do not show this error message. + * When error is different than EEXIST then pppd tries to fallback to + * the old ioctl method. + */ + errno = (nlresp.nlerr.error < 0) ? -nlresp.nlerr.error : EINVAL; + if (kernel_version >= KVERSION(4,7,0)) + error("Couldn't create ppp interface %s: %m", req_ifname); + return 0; + } + + return 1; +} + /* * make_ppp_unit - make a new ppp unit for ppp_dev_fd. * Assumes new_style_driver. @@ -650,6 +840,33 @@ static int make_ppp_unit(void) || fcntl(ppp_dev_fd, F_SETFL, flags | O_NONBLOCK) == -1) warn("Couldn't set /dev/ppp to nonblock: %m"); + /* + * Via rtnetlink it is possible to create ppp network interface with + * custom ifname atomically. But it is not possible to specify custom + * ppp unit id. + * + * Tools like systemd, udev or NetworkManager are trying to query + * interface attributes based on interface name immediately when new + * network interface is created. And therefore immediate interface + * renaming is causing issues. + * + * So use rtnetlink API only when user requested custom ifname. It will + * avoid system issues with interface renaming. + */ + if (req_unit == -1 && req_ifname[0] != '\0' && kernel_version >= KVERSION(2,1,16)) { + if (make_ppp_unit_rtnetlink()) { + if (ioctl(ppp_dev_fd, PPPIOCGUNIT, &ifunit)) + fatal("Couldn't retrieve PPP unit id: %m"); + return 0; + } + /* + * If interface with requested name already exist return error + * otherwise fallback to old ioctl method. + */ + if (errno == EEXIST) + return -1; + } + ifunit = req_unit; x = ioctl(ppp_dev_fd, PPPIOCNEWUNIT, &ifunit); if (x < 0 && req_unit >= 0 && errno == EEXIST) { @@ -662,11 +879,11 @@ static int make_ppp_unit(void) if (x == 0 && req_ifname[0] != '\0') { struct ifreq ifr; - char t[MAXIFNAMELEN]; + char t[IFNAMSIZ]; memset(&ifr, 0, sizeof(struct ifreq)); slprintf(t, sizeof(t), "%s%d", PPP_DRV_NAME, ifunit); - strlcpy(ifr.ifr_name, t, IF_NAMESIZE); - strlcpy(ifr.ifr_newname, req_ifname, IF_NAMESIZE); + strlcpy(ifr.ifr_name, t, IFNAMSIZ); + strlcpy(ifr.ifr_newname, req_ifname, IFNAMSIZ); x = ioctl(sock_fd, SIOCSIFNAME, &ifr); if (x < 0) error("Couldn't rename interface %s to %s: %m", t, req_ifname); @@ -872,6 +1089,9 @@ struct speed { #ifdef B115200 { 115200, B115200 }, #endif +#ifdef B153600 + { 153600, B153600 }, +#endif #ifdef EXTA { 19200, EXTA }, #endif @@ -881,9 +1101,21 @@ struct speed { #ifdef B230400 { 230400, B230400 }, #endif +#ifdef B307200 + { 307200, B307200 }, +#endif #ifdef B460800 { 460800, B460800 }, #endif +#ifdef B500000 + { 500000, B500000 }, +#endif +#ifdef B576000 + { 576000, B576000 }, +#endif +#ifdef B614400 + { 614400, B614400 }, +#endif #ifdef B921600 { 921600, B921600 }, #endif @@ -928,7 +1160,6 @@ static int translate_speed (int bps) if (bps == speedp->speed_int) return speedp->speed_val; } - warn("speed %d not supported", bps); } return 0; } @@ -1007,26 +1238,53 @@ void set_up_tty(int tty_fd, int local) if (stop_bits >= 2) tios.c_cflag |= CSTOPB; - speed = translate_speed(inspeed); - if (speed) { - cfsetospeed (&tios, speed); - cfsetispeed (&tios, speed); + if (inspeed) { + speed = translate_speed(inspeed); + if (speed) { + 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 + } + } + else { + speed = cfgetospeed(&tios); + baud_rate = baud_rate_of(speed); +#ifdef BOTHER + if (!baud_rate) + baud_rate = tios.c_ospeed; +#endif } + /* - * We can't proceed if the serial port speed is B0, + * We can't proceed if the serial port baud rate is unknown, * since that implies that the serial port is disabled. */ - else { - speed = cfgetospeed(&tios); - if (speed == B0) + if (!baud_rate) { + if (inspeed) + fatal("speed %d not supported", inspeed); + 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__); - - baud_rate = baud_rate_of(speed); restore_term = 1; } @@ -1393,11 +1651,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)); @@ -1412,9 +1676,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 @@ -2800,7 +3319,7 @@ int cifaddr (int unit, u_int32_t our_adr, u_int32_t his_adr) } } - /* 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)); @@ -2818,6 +3337,157 @@ int cifaddr (int unit, u_int32_t our_adr, u_int32_t his_adr) } #ifdef INET6 +/******************************************************************** + * + * sif6addr_rtnetlink - Config the interface with both IPv6 link-local addresses via rtnetlink + */ +static int sif6addr_rtnetlink(unsigned int iface, eui64_t our_eui64, eui64_t his_eui64) +{ + struct { + struct nlmsghdr nlh; + struct ifaddrmsg ifa; + struct { + struct rtattr rta; + struct in6_addr addr; + } addrs[2]; + } nlreq; + struct { + struct nlmsghdr nlh; + struct nlmsgerr nlerr; + } nlresp; + struct sockaddr_nl nladdr; + struct iovec iov; + struct msghdr msg; + ssize_t nlresplen; + int one; + int fd; + + fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (fd < 0) { + error("sif6addr_rtnetlink: socket(NETLINK_ROUTE): %m (line %d)", __LINE__); + return 0; + } + + /* + * Tell kernel to not send to us payload of acknowledgment error message. + * NETLINK_CAP_ACK option is supported since Linux kernel version 4.3 and + * older kernel versions always send full payload in acknowledgment netlink + * message. We ignore payload of this message as we need only error code, + * to check if our set remote peer address request succeeded or failed. + * So ignore return value from the following setsockopt() call as setting + * option NETLINK_CAP_ACK means for us just a kernel hint / optimization. + */ + one = 1; + setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, &one, sizeof(one)); + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + + if (bind(fd, (struct sockaddr *)&nladdr, sizeof(nladdr)) < 0) { + error("sif6addr_rtnetlink: bind(AF_NETLINK): %m (line %d)", __LINE__); + close(fd); + return 0; + } + + memset(&nlreq, 0, sizeof(nlreq)); + nlreq.nlh.nlmsg_len = sizeof(nlreq); + nlreq.nlh.nlmsg_type = RTM_NEWADDR; + nlreq.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE; + nlreq.ifa.ifa_family = AF_INET6; + nlreq.ifa.ifa_prefixlen = 128; + nlreq.ifa.ifa_flags = IFA_F_NODAD | IFA_F_PERMANENT; + nlreq.ifa.ifa_scope = RT_SCOPE_LINK; + nlreq.ifa.ifa_index = iface; + nlreq.addrs[0].rta.rta_len = sizeof(nlreq.addrs[0]); + nlreq.addrs[0].rta.rta_type = IFA_LOCAL; + 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; + + /* + * 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; + + 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(fd, &msg, 0) < 0) { + error("sif6addr_rtnetlink: sendmsg(RTM_NEWADDR/NLM_F_CREATE): %m (line %d)", __LINE__); + close(fd); + return 0; + } + + memset(&iov, 0, sizeof(iov)); + iov.iov_base = &nlresp; + iov.iov_len = sizeof(nlresp); + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = &nladdr; + msg.msg_namelen = sizeof(nladdr); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + nlresplen = recvmsg(fd, &msg, 0); + + if (nlresplen < 0) { + error("sif6addr_rtnetlink: recvmsg(NLM_F_ACK): %m (line %d)", __LINE__); + close(fd); + return 0; + } + + close(fd); + + if (nladdr.nl_family != AF_NETLINK) { + error("sif6addr_rtnetlink: recvmsg(NLM_F_ACK): Not a netlink packet (line %d)", __LINE__); + return 0; + } + + if ((size_t)nlresplen != sizeof(nlresp) || nlresp.nlh.nlmsg_len < sizeof(nlresp)) { + error("sif6addr_rtnetlink: recvmsg(NLM_F_ACK): Acknowledgment netlink packet too short (line %d)", __LINE__); + return 0; + } + + /* acknowledgment packet for NLM_F_ACK is NLMSG_ERROR */ + if (nlresp.nlh.nlmsg_type != NLMSG_ERROR) { + error("sif6addr_rtnetlink: recvmsg(NLM_F_ACK): Not an acknowledgment netlink packet (line %d)", __LINE__); + return 0; + } + + /* error == 0 indicates success, negative value is errno code */ + if (nlresp.nlerr.error != 0) { + /* + * Linux kernel versions prior 3.11 do not support setting IPv6 peer + * addresses and error response is expected. On older kernel versions + * do not show this error message. On error pppd tries to fallback to + * the old IOCTL method. + */ + if (kernel_version >= KVERSION(3,11,0)) + error("sif6addr_rtnetlink: %s (line %d)", strerror(-nlresp.nlerr.error), __LINE__); + return 0; + } + + return 1; +} + /******************************************************************** * * sif6addr - Config the interface with an IPv6 link-local address @@ -2827,6 +3497,7 @@ int sif6addr (int unit, eui64_t our_eui64, eui64_t his_eui64) struct in6_ifreq ifr6; struct ifreq ifr; struct in6_rtmsg rt6; + int ret; if (sock6_fd < 0) { errno = -sock6_fd; @@ -2840,28 +3511,48 @@ int sif6addr (int unit, eui64_t our_eui64, eui64_t his_eui64) return 0; } - /* Local interface */ - memset(&ifr6, 0, sizeof(ifr6)); - IN6_LLADDR_FROM_EUI64(ifr6.ifr6_addr, our_eui64); - ifr6.ifr6_ifindex = ifr.ifr_ifindex; - ifr6.ifr6_prefixlen = 128; - - if (ioctl(sock6_fd, SIOCSIFADDR, &ifr6) < 0) { - error("sif6addr: ioctl(SIOCSIFADDR): %m (line %d)", __LINE__); - return 0; + if (kernel_version >= KVERSION(2,1,16)) { + /* Set both local address and remote peer address (with route for it) via rtnetlink */ + ret = sif6addr_rtnetlink(ifr.ifr_ifindex, our_eui64, his_eui64); + } else { + ret = 0; } - /* Route to remote host */ - memset(&rt6, 0, sizeof(rt6)); - IN6_LLADDR_FROM_EUI64(rt6.rtmsg_dst, his_eui64); - rt6.rtmsg_flags = RTF_UP; - rt6.rtmsg_dst_len = 128; - rt6.rtmsg_ifindex = ifr.ifr_ifindex; - rt6.rtmsg_metric = 1; - - if (ioctl(sock6_fd, SIOCADDRT, &rt6) < 0) { - error("sif6addr: ioctl(SIOCADDRT): %m (line %d)", __LINE__); - return 0; + /* + * Linux kernel versions prior 3.11 do not support setting IPv6 peer address + * via rtnetlink. So if sif6addr_rtnetlink() fails then try old IOCTL method. + */ + if (!ret) { + /* Local interface */ + memset(&ifr6, 0, sizeof(ifr6)); + IN6_LLADDR_FROM_EUI64(ifr6.ifr6_addr, our_eui64); + ifr6.ifr6_ifindex = ifr.ifr_ifindex; + ifr6.ifr6_prefixlen = 128; + + if (ioctl(sock6_fd, SIOCSIFADDR, &ifr6) < 0) { + 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. + */ + + /* Route to remote host */ + memset(&rt6, 0, sizeof(rt6)); + IN6_LLADDR_FROM_EUI64(rt6.rtmsg_dst, his_eui64); + rt6.rtmsg_flags = RTF_UP; + rt6.rtmsg_dst_len = 128; + rt6.rtmsg_ifindex = ifr.ifr_ifindex; + rt6.rtmsg_metric = 1; + + if (ioctl(sock6_fd, SIOCADDRT, &rt6) < 0) { + error("sif6addr: ioctl(SIOCADDRT): %m (line %d)", __LINE__); + return 0; + } } return 1; @@ -2915,7 +3606,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; @@ -2953,8 +3644,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); @@ -3053,99 +3750,6 @@ sifnpmode(int u, int proto, enum NPmode mode) return 1; } - -/******************************************************************** - * - * 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. */ @@ -3169,22 +3773,6 @@ get_host_seed(void) 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,