X-Git-Url: http://git.ozlabs.org/?p=ppp.git;a=blobdiff_plain;f=pppd%2Fsys-linux.c;fp=pppd%2Fsys-linux.c;h=dc3b4d658de4770c32a95900d9f811b4c7fc5878;hp=9c0455c0da62e4fb31ed0e4b3ae542bab293844d;hb=87640b7b5ceb562146a1c4b820c7e151ff078eec;hpb=8cbd7dd098cbb565dd9b01397fb352f1c98376f3 diff --git a/pppd/sys-linux.c b/pppd/sys-linux.c index 9c0455c..dc3b4d6 100644 --- a/pppd/sys-linux.c +++ b/pppd/sys-linux.c @@ -298,6 +298,147 @@ extern int dfl_route_metric; memset ((char *) &(addr), '\0', sizeof(addr)); \ addr.sa_family = (family); + +/* + * rtnetlink_msg - send rtnetlink message, receive response + * and return received error code: + * 0 - success + * positive value - error during sending / receiving message + * negative value - rtnetlink responce error code + */ +static int rtnetlink_msg(const char *desc, int *shared_fd, void *nlreq, size_t nlreq_len, void *nlresp_data, size_t *nlresp_size, unsigned nlresp_type) +{ + struct nlresp_hdr { + struct nlmsghdr nlh; + struct nlmsgerr nlerr; + } nlresp_hdr; + struct sockaddr_nl nladdr; + struct iovec iov[2]; + struct msghdr msg; + ssize_t nlresp_len; + int one; + int fd; + + if (shared_fd && *shared_fd >= 0) { + fd = *shared_fd; + } else { + fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (fd < 0) { + error("rtnetlink_msg: socket(NETLINK_ROUTE): %m (line %d)", __LINE__); + return 1; + } + + /* + * 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("rtnetlink_msg: bind(AF_NETLINK): %m (line %d)", __LINE__); + close(fd); + return 1; + } + + if (shared_fd) + *shared_fd = fd; + } + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + + memset(&iov[0], 0, sizeof(iov[0])); + iov[0].iov_base = nlreq; + iov[0].iov_len = nlreq_len; + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = &nladdr; + msg.msg_namelen = sizeof(nladdr); + msg.msg_iov = &iov[0]; + msg.msg_iovlen = 1; + + if (sendmsg(fd, &msg, 0) < 0) { + error("rtnetlink_msg: sendmsg(%s): %m (line %d)", desc, __LINE__); + if (!shared_fd) + close(fd); + return 1; + } + + memset(iov, 0, sizeof(iov)); + iov[0].iov_base = &nlresp_hdr; + if (nlresp_size && *nlresp_size > sizeof(nlresp_hdr)) { + iov[0].iov_len = offsetof(struct nlresp_hdr, nlerr); + iov[1].iov_base = nlresp_data; + iov[1].iov_len = *nlresp_size; + } else { + iov[0].iov_len = sizeof(nlresp_hdr); + } + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = &nladdr; + msg.msg_namelen = sizeof(nladdr); + msg.msg_iov = iov; + msg.msg_iovlen = (nlresp_size && *nlresp_size > sizeof(nlresp_hdr)) ? 2 : 1; + + nlresp_len = recvmsg(fd, &msg, 0); + + if (!shared_fd) + close(fd); + + if (nlresp_len < 0) { + error("rtnetlink_msg: recvmsg(%s): %m (line %d)", desc, __LINE__); + return 1; + } + + if (nladdr.nl_family != AF_NETLINK) { + error("rtnetlink_msg: recvmsg(%s): Not a netlink packet (line %d)", desc, __LINE__); + return 1; + } + + if (!nlresp_size) { + if ((size_t)nlresp_len < sizeof(nlresp_hdr) || nlresp_hdr.nlh.nlmsg_len < sizeof(nlresp_hdr)) { + error("rtnetlink_msg: recvmsg(%s): Acknowledgment netlink packet too short (line %d)", desc, __LINE__); + return 1; + } + + /* acknowledgment packet for NLM_F_ACK is NLMSG_ERROR */ + if (nlresp_hdr.nlh.nlmsg_type != NLMSG_ERROR) { + error("rtnetlink_msg: recvmsg(%s): Not an acknowledgment netlink packet (line %d)", desc, __LINE__); + return 1; + } + } + + if (nlresp_size) { + if (*nlresp_size > sizeof(nlresp_hdr)) + memcpy((unsigned char *)&nlresp_hdr + offsetof(struct nlresp_hdr, nlerr), nlresp_data, sizeof(nlresp_hdr.nlerr)); + else + memcpy(nlresp_data, (unsigned char *)&nlresp_hdr + offsetof(struct nlresp_hdr, nlerr), *nlresp_size); + } + + /* error == 0 indicates success, negative value is errno code */ + if (nlresp_hdr.nlh.nlmsg_type == NLMSG_ERROR && nlresp_hdr.nlerr.error) + return nlresp_hdr.nlerr.error; + + if (nlresp_size) { + if (nlresp_hdr.nlh.nlmsg_type != nlresp_type) { + error("rtnetlink_msg: recvmsg(%s): Not a netlink packet of type 0x%x (line %d)", desc, nlresp_type, __LINE__); + return 1; + } + *nlresp_size = nlresp_len - offsetof(struct nlresp_hdr, nlerr); + } + + return 0; +} + /* * Determine if the PPP connection should still be present. */ @@ -701,35 +842,7 @@ static int make_ppp_unit_rtnetlink(void) } 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; - } + int resp; memset(&nlreq, 0, sizeof(nlreq)); nlreq.nlh.nlmsg_len = sizeof(nlreq); @@ -751,63 +864,8 @@ static int make_ppp_unit_rtnetlink(void) 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) { + resp = rtnetlink_msg("RTM_NEWLINK/NLM_F_CREATE", NULL, &nlreq, sizeof(nlreq), NULL, NULL, 0); + if (resp) { /* * Linux kernel versions prior to 4.7 do not support creating ppp * interfaces via rtnetlink API and therefore error response is @@ -815,7 +873,7 @@ static int make_ppp_unit_rtnetlink(void) * 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; + errno = (resp < 0) ? -resp : EINVAL; if (kernel_version >= KVERSION(4,7,0)) error("Couldn't create ppp interface %s: %m", req_ifname); return 0; @@ -1708,120 +1766,55 @@ get_ppp_stats_ioctl(int u, struct pppd_stats *stats) static int get_ppp_stats_rtnetlink(int u, struct pppd_stats *stats) { - static int rtnl_fd = -1; + static int 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; - } - } + 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; + } nlresp_data; + size_t nlresp_size; + int resp; 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)) - error("get_ppp_stats_rtnetlink: Netlink responded with an error message, but the nlmsgerr structure is incomplete (line %d).", __LINE__); - else if (kernel_version >= KVERSION(4,7,0)) - error("get_ppp_stats_rtnetlink: Netlink responded with error: %s (line %d)", strerror(-nlresp.nlerr.error), __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; + nlresp_size = sizeof(nlresp_data); + resp = rtnetlink_msg("RTM_GETSTATS/NLM_F_REQUEST", &fd, &nlreq, sizeof(nlreq), &nlresp_data, &nlresp_size, RTM_NEWSTATS); + if (resp) { + errno = (resp < 0) ? -resp : EINVAL; + if (kernel_version >= KVERSION(4,7,0)) + error("get_ppp_stats_rtnetlink: %m (line %d)", __LINE__); + goto err; } - if (nlresplen < offsetof(struct nlresp, __end_stats)) { + if (nlresp_size < sizeof(nlresp_data)) { 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; + stats->bytes_in = nlresp_data.stats.rx_bytes; + stats->bytes_out = nlresp_data.stats.tx_bytes; + stats->pkts_in = nlresp_data.stats.rx_packets; + stats->pkts_out = nlresp_data.stats.tx_packets; return 1; err: - close(rtnl_fd); - rtnl_fd = -1; + close(fd); + fd = -1; return 0; } @@ -3358,43 +3351,7 @@ static int sif6addr_rtnetlink(unsigned int iface, eui64_t our_eui64, eui64_t his 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; - } + int resp; memset(&nlreq, 0, sizeof(nlreq)); nlreq.nlh.nlmsg_len = sizeof(nlreq); @@ -3424,71 +3381,17 @@ static int sif6addr_rtnetlink(unsigned int iface, eui64_t our_eui64, eui64_t his 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) { + resp = rtnetlink_msg("RTM_NEWADDR/NLM_F_CREATE", NULL, &nlreq, sizeof(nlreq), NULL, NULL, 0); + if (resp) { /* * 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. */ + errno = (resp < 0) ? -resp : EINVAL; if (kernel_version >= KVERSION(3,11,0)) - error("sif6addr_rtnetlink: %s (line %d)", strerror(-nlresp.nlerr.error), __LINE__); + error("sif6addr_rtnetlink: %m (line %d)", __LINE__); return 0; }