#include <sys/stat.h>
#include <sys/utsname.h>
#include <sys/sysmacros.h>
+#include <sys/param.h>
#include <errno.h>
#include <stddef.h>
#include <netinet/in.h>
#include <arpa/inet.h>
-#include <linux/ppp_defs.h>
-#include <linux/if_ppp.h>
+#include <linux/ppp-ioctl.h>
#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.
*/
#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
#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 PPP_FILTER
+#ifdef PPP_WITH_IPV6CP
+#include "eui64.h"
+#endif /* PPP_WITH_IPV6CP */
+
+#ifdef PPP_WITH_FILTER
#include <pcap-bpf.h>
#include <linux/filter.h>
-#endif /* PPP_FILTER */
+#endif /* PPP_WITH_FILTER */
#ifdef LOCKLIB
#include <sys/locks.h>
*/
#include "termios_linux.h"
-#ifdef INET6
+#ifdef PPP_WITH_IPV6CP
#ifndef _LINUX_IN6_H
/*
* This is in linux/include/net/ipv6.h.
} while (0)
static const eui64_t nulleui64;
-#endif /* INET6 */
+#endif /* PPP_WITH_IPV6CP */
/* We can get an EIO error on an ioctl if the modem has hung up */
#define ok_error(num) ((num)==EIO)
static int sock_fd = -1; /* socket for doing interface ioctls */
static int slave_fd = -1; /* pty for old-style demand mode, slave */
static int master_fd = -1; /* pty for old-style demand mode, master */
-#ifdef INET6
+#ifdef PPP_WITH_IPV6CP
static int sock6_fd = -1;
-#endif /* INET6 */
+#endif /* PPP_WITH_IPV6CP */
/*
* For the old-style kernel driver, this is the same as ppp_fd.
if (sock_fd < 0)
fatal("Couldn't create IP socket: %m(%d)", errno);
-#ifdef INET6
+#ifdef PPP_WITH_IPV6CP
sock6_fd = socket(AF_INET6, SOCK_DGRAM, 0);
if (sock6_fd < 0)
sock6_fd = -errno; /* save errno for later */
if_is_up = 0;
sifdown(0);
}
+#ifdef PPP_WITH_IPV6CP
if (if6_is_up)
sif6down(0);
+#endif
/*
* Delete any routes through the device.
*/
if (have_default_route)
cifdefaultroute(0, 0, 0);
-#ifdef INET6
+#ifdef PPP_WITH_IPV6CP
if (have_default_route6)
cif6defaultroute(0, nulleui64, nulleui64);
#endif
close(ppp_dev_fd);
if (sock_fd >= 0)
close(sock_fd);
-#ifdef INET6
+#ifdef PPP_WITH_IPV6CP
if (sock6_fd >= 0)
close(sock6_fd);
#endif
}
}
+/*
+ * 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.
|| 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) {
ifunit = -1;
x = ioctl(ppp_dev_fd, PPPIOCNEWUNIT, &ifunit);
}
+ if (x < 0 && errno == EEXIST) {
+ srand(time(NULL) * getpid());
+ ifunit = rand() % 10000;
+ x = ioctl(ppp_dev_fd, PPPIOCNEWUNIT, &ifunit);
+ }
if (x < 0)
error("Couldn't create new ppp unit: %m");
modify_flags(ppp_dev_fd, SC_CCP_OPEN|SC_CCP_UP, x);
}
-#ifdef PPP_FILTER
+#ifdef PPP_WITH_FILTER
/*
* set_filters - set the active and pass filters in the kernel driver.
*/
}
return 1;
}
-#endif /* PPP_FILTER */
+#endif /* PPP_WITH_FILTER */
/********************************************************************
*
static u_int32_t iwraps = 0;
static u_int32_t owraps = 0;
- struct ifpppstatsreq req;
+ struct ifreq req;
+ struct ppp_stats data;
memset (&req, 0, sizeof (req));
- req.stats_ptr = (caddr_t) &req.stats;
- strlcpy(req.ifr__name, ifname, sizeof(req.ifr__name));
+ req.ifr_data = (caddr_t) &data;
+ strlcpy(req.ifr_name, ifname, sizeof(req.ifr_name));
if (ioctl(sock_fd, SIOCGPPPSTATS, &req) < 0) {
error("Couldn't get PPP statistics: %m");
return 0;
}
- stats->bytes_in = req.stats.p.ppp_ibytes;
- stats->bytes_out = req.stats.p.ppp_obytes;
- stats->pkts_in = req.stats.p.ppp_ipackets;
- stats->pkts_out = req.stats.p.ppp_opackets;
+ stats->bytes_in = data.p.ppp_ibytes;
+ stats->bytes_out = data.p.ppp_obytes;
+ stats->pkts_in = data.p.ppp_ipackets;
+ stats->pkts_out = data.p.ppp_opackets;
if (stats->bytes_in < previbytes)
++iwraps;
}
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__);
- }
+ 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;
}
return 1;
}
-#ifdef INET6
+#ifdef PPP_WITH_IPV6CP
/*
* /proc/net/ipv6_route parsing stuff.
*/
return 1;
}
-#endif /* INET6 */
+#endif /* PPP_WITH_IPV6CP */
/********************************************************************
*
if (if_is_up && --if_is_up > 0)
return 1;
-#ifdef INET6
+#ifdef PPP_WITH_IPV6CP
if (if6_is_up)
return 1;
-#endif /* INET6 */
+#endif /* PPP_WITH_IPV6CP */
return setifstate(u, 0);
}
-#ifdef INET6
+#ifdef PPP_WITH_IPV6CP
/********************************************************************
*
* sif6up - Config the interface up for IPv6
return setifstate(u, 0);
}
-#endif /* INET6 */
+#endif /* PPP_WITH_IPV6CP */
/********************************************************************
*
return 1;
}
-#ifdef INET6
+#ifdef PPP_WITH_IPV6CP
/********************************************************************
*
* sif6addr_rtnetlink - Config the interface with both IPv6 link-local addresses via rtnetlink
}
return 1;
}
-#endif /* INET6 */
+#endif /* PPP_WITH_IPV6CP */
/*
* get_pty - get a pty master/slave pair and chown the slave side