]> git.ozlabs.org Git - ppp.git/blobdiff - pppd/sys-linux.c
Makefile.am: Add explicit openssl directory to pppd include path
[ppp.git] / pppd / sys-linux.c
index 6eed86f3f2a2b0bd6f1754375d1c6734380ce6a2..c0955a05b9a89dbd60afe4e51821df9473646a38 100644 (file)
@@ -18,7 +18,7 @@
  * 3. Redistributions of any form whatsoever must retain the following
  *    acknowledgment:
  *    "This product includes software developed by Paul Mackerras
- *     <paulus@samba.org>".
+ *     <paulus@ozlabs.org>".
  *
  * THE AUTHORS OF THIS SOFTWARE DISCLAIM ALL WARRANTIES WITH REGARD TO
  * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
@@ -81,6 +81,7 @@
 #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>
-
-#ifdef INET6
 #include <linux/if_addr.h>
-#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 IFLA_PPP_DEV_FD 1
 #endif
 
-#include "pppd.h"
+#include "pppd-private.h"
+#include "options.h"
 #include "fsm.h"
 #include "ipcp.h"
 
-#ifdef PPP_FILTER
+#ifdef PPP_WITH_IPV6CP
+#include "eui64.h"
+#endif /* PPP_WITH_IPV6CP */
+
+#include "multilink.h"
+
+#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.
@@ -200,7 +195,7 @@ struct in6_ifreq {
        } 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)
@@ -212,9 +207,9 @@ static int ppp_fd = -1;             /* fd which is set to PPP discipline */
 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.
@@ -296,6 +291,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.
  */
@@ -357,7 +493,7 @@ void sys_init(void)
     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 */
@@ -383,15 +519,17 @@ void sys_cleanup(void)
        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
@@ -402,16 +540,16 @@ void sys_cleanup(void)
 
 /********************************************************************
  *
- * sys_close - Clean up in a child process before execing.
+ * ppp_sys_close - Clean up in a child process before execing.
  */
 void
-sys_close(void)
+ppp_sys_close(void)
 {
     if (new_style_driver && ppp_dev_fd >= 0)
        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
@@ -469,7 +607,7 @@ int tty_establish_ppp (int tty_fd)
 #ifndef N_SYNC_PPP
 #define N_SYNC_PPP 14
 #endif
-    ppp_disc = (new_style_driver && sync_serial)? N_SYNC_PPP: N_PPP;
+    ppp_disc = (new_style_driver && ppp_sync_serial())? N_SYNC_PPP: N_PPP;
     if (ioctl(tty_fd, TIOCSETD, &ppp_disc) < 0) {
        if ( ! ok_error (errno) ) {
            error("Couldn't set tty to PPP discipline: %m");
@@ -477,7 +615,7 @@ int tty_establish_ppp (int tty_fd)
        }
     }
 
-    ret_fd = generic_establish_ppp(tty_fd);
+    ret_fd = ppp_generic_establish(tty_fd);
 
 #define SC_RCVB        (SC_RCV_B7_0 | SC_RCV_B7_1 | SC_RCV_EVNP | SC_RCV_ODDP)
 #define SC_LOGB        (SC_DEBUG | SC_LOG_INPKT | SC_LOG_OUTPKT | SC_LOG_RAWIN \
@@ -498,7 +636,7 @@ int tty_establish_ppp (int tty_fd)
  *
  * generic_establish_ppp - Turn the fd into a ppp interface.
  */
-int generic_establish_ppp (int fd)
+int ppp_generic_establish (int fd)
 {
     int x;
 
@@ -635,16 +773,16 @@ void tty_disestablish_ppp(int tty_fd)
 flushfailed:
     initfdflags = -1;
 
-    generic_disestablish_ppp(tty_fd);
+    ppp_generic_disestablish(tty_fd);
 }
 
 /********************************************************************
  *
- * generic_disestablish_ppp - Restore device components to normal
+ * ppp_generic_disestablish - Restore device components to normal
  * operation, and reconnect the ppp unit to the loopback if in demand
  * mode.  This shouldn't call die() because it's called from die().
  */
-void generic_disestablish_ppp(int dev_fd)
+void ppp_generic_disestablish(int dev_fd)
 {
     if (new_style_driver) {
        close(ppp_fd);
@@ -652,7 +790,7 @@ void generic_disestablish_ppp(int dev_fd)
        if (demand) {
            modify_flags(ppp_dev_fd, 0, SC_LOOP_TRAFFIC);
            looped = 1;
-       } else if (!doing_multilink && ppp_dev_fd >= 0) {
+       } else if (!mp_on() && ppp_dev_fd >= 0) {
            close(ppp_dev_fd);
            remove_fd(ppp_dev_fd);
            ppp_dev_fd = -1;
@@ -697,35 +835,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);
@@ -747,63 +857,15 @@ 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;
-    }
+    /*
+     * See kernel function ppp_nl_newlink(), which may return -EBUSY to prevent
+     * possible deadlock in kernel and ask userspace to retry request again.
+     */
+    do {
+        resp = rtnetlink_msg("RTM_NEWLINK/NLM_F_CREATE", NULL, &nlreq, sizeof(nlreq), NULL, NULL, 0);
+    } while (resp == -EBUSY);
 
-    /* error == 0 indicates success, negative value is errno code */
-    if (nlresp.nlerr.error != 0) {
+    if (resp) {
         /*
          * Linux kernel versions prior to 4.7 do not support creating ppp
          * interfaces via rtnetlink API and therefore error response is
@@ -811,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;
@@ -874,6 +936,11 @@ static int make_ppp_unit(void)
                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");
 
@@ -1429,7 +1496,7 @@ int read_packet (unsigned char *buf)
            error("read /dev/ppp: %m");
        if (nr < 0 && errno == ENXIO)
            nr = 0;
-       if (nr == 0 && doing_multilink) {
+       if (nr == 0 && mp_on()) {
            remove_fd(ppp_dev_fd);
            bundle_eof = 1;
        }
@@ -1475,7 +1542,7 @@ get_loop_output(void)
  * netif_set_mtu - set the MTU on the PPP network interface.
  */
 void
-netif_set_mtu(int unit, int mtu)
+ppp_set_mtu(int unit, int mtu)
 {
     struct ifreq ifr;
 
@@ -1491,7 +1558,7 @@ netif_set_mtu(int unit, int mtu)
  * netif_get_mtu - get the MTU on the PPP network interface.
  */
 int
-netif_get_mtu(int unit)
+ppp_get_mtu(int unit)
 {
     struct ifreq ifr;
 
@@ -1526,7 +1593,7 @@ void tty_send_config(int mtu, u_int32_t asyncmap, int pcomp, int accomp)
        }
 
        x = (pcomp? SC_COMP_PROT: 0) | (accomp? SC_COMP_AC: 0)
-           | (sync_serial? SC_SYNC: 0);
+           | (ppp_sync_serial()? SC_SYNC: 0);
        modify_flags(ppp_fd, SC_COMP_PROT|SC_COMP_AC|SC_SYNC, x);
 }
 
@@ -1612,7 +1679,7 @@ void ccp_flags_set (int unit, int isopen, int isup)
                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.
  */
@@ -1637,7 +1704,7 @@ int set_filters(struct bpf_program *pass, struct bpf_program *active)
        }
        return 1;
 }
-#endif /* PPP_FILTER */
+#endif /* PPP_WITH_FILTER */
 
 /********************************************************************
  *
@@ -1662,20 +1729,21 @@ get_ppp_stats_ioctl(int u, struct pppd_stats *stats)
     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;
@@ -1698,123 +1766,57 @@ 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;
+#ifdef RTM_NEWSTATS
+    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)) {
-           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;
+    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;
+#endif
     return 0;
 }
 
@@ -2191,11 +2193,27 @@ int sifdefaultroute (int unit, u_int32_t ouraddr, u_int32_t gateway, bool replac
         * - this is normally only the case the doing demand: */
        if (defaultroute_exists(&tmp_rt, -1))
            del_rt = &tmp_rt;
+    } else if (!replace) {
+       /*
+        * We don't want to replace an existing route.
+        * We may however add our route along an existing route with a different
+        * metric.
+        */
+       if (defaultroute_exists(&rt, dfl_route_metric) && strcmp(rt.rt_dev, ifname) != 0) {
+          if (rt.rt_flags & RTF_GATEWAY)
+              error("not replacing existing default route via %I with metric %d",
+                    SIN_ADDR(rt.rt_gateway), dfl_route_metric);
+          else
+              error("not replacing existing default route through %s with metric %d",
+                    rt.rt_dev, dfl_route_metric);
+          return 0;
+       }
     } else if (defaultroute_exists(&old_def_rt, -1           ) &&
                            strcmp( old_def_rt.rt_dev, ifname) != 0) {
        /*
-        * We did not yet replace an existing default route, let's
-        * check if we should save and replace a default route:
+        * We want to replace an existing route and did not replace an existing
+        * default route yet, let's check if we should save and replace an
+        * existing default route:
         */
        u_int32_t old_gateway = SIN_ADDR(old_def_rt.rt_gateway);
 
@@ -2293,7 +2311,7 @@ int cifdefaultroute (int unit, u_int32_t ouraddr, u_int32_t gateway)
     return 1;
 }
 
-#ifdef INET6
+#ifdef PPP_WITH_IPV6CP
 /*
  * /proc/net/ipv6_route parsing stuff.
  */
@@ -2483,7 +2501,7 @@ int cif6defaultroute (int unit, eui64_t ouraddr, eui64_t gateway)
 
     return 1;
 }
-#endif /* INET6 */
+#endif /* PPP_WITH_IPV6CP */
 
 /********************************************************************
  *
@@ -2850,11 +2868,11 @@ ppp_registered(void)
 
 /********************************************************************
  *
- * ppp_available - check whether the system has any ppp interfaces
+ * ppp_check_kernel_support - check whether the system has any ppp interfaces
  * (in fact we check whether we can do an ioctl on ppp0).
  */
 
-int ppp_available(void)
+int ppp_check_kernel_support(void)
 {
     int s, ok, fd;
     struct ifreq ifr;
@@ -3109,15 +3127,15 @@ int sifdown (int u)
     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
@@ -3148,7 +3166,7 @@ int sif6down (int u)
 
     return setifstate(u, 0);
 }
-#endif /* INET6 */
+#endif /* PPP_WITH_IPV6CP */
 
 /********************************************************************
  *
@@ -3336,7 +3354,7 @@ int cifaddr (int unit, u_int32_t our_adr, u_int32_t his_adr)
     return 1;
 }
 
-#ifdef INET6
+#ifdef PPP_WITH_IPV6CP
 /********************************************************************
  *
  * sif6addr_rtnetlink - Config the interface with both IPv6 link-local addresses via rtnetlink
@@ -3351,43 +3369,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);
@@ -3417,71 +3399,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;
     }
 
@@ -3597,7 +3525,7 @@ int cif6addr (int unit, eui64_t our_eui64, eui64_t his_eui64)
     }
     return 1;
 }
-#endif /* INET6 */
+#endif /* PPP_WITH_IPV6CP */
 
 /*
  * get_pty - get a pty master/slave pair and chown the slave side
@@ -3757,7 +3685,7 @@ int
 get_host_seed(void)
 {
     int h;
-    char *p = hostname;
+    const char *p;
 
     h = 407;
     for (p = hostname; *p != 0; ++p)
@@ -3774,7 +3702,7 @@ int
 sys_check_options(void)
 {
     if (demand && driver_is_old) {
-       option_error("demand dialling is not supported by kernel driver "
+       ppp_option_error("demand dialling is not supported by kernel driver "
                     "version %d.%d.%d", driver_version, driver_modification,
                     driver_patch);
        return 0;
@@ -3791,7 +3719,7 @@ sys_check_options(void)
  * get_time - Get current time, monotonic if possible.
  */
 int
-get_time(struct timeval *tv)
+ppp_get_time(struct timeval *tv)
 {
 /* Old glibc (< 2.3.4) does define CLOCK_MONOTONIC, but kernel may have it.
  * Runtime checking makes it safe. */