]> git.ozlabs.org Git - ppp.git/blobdiff - pppd/sys-linux.c
pppd man page: Update header to refer to pppd 2.5.x
[ppp.git] / pppd / sys-linux.c
index ff3a249e81758ec042a6cd67991e6412532f8d1b..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
  * 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
  *
  * THE AUTHORS OF THIS SOFTWARE DISCLAIM ALL WARRANTIES WITH REGARD TO
  * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
 #include <linux/netlink.h>
 #include <linux/rtnetlink.h>
 #include <linux/if_link.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.
- */
-#ifndef RTM_NEWSTATS
-#define RTM_NEWSTATS 92
-#define RTM_GETSTATS 94
-#define IFLA_STATS_LINK_64 1
-
 #include <linux/if_addr.h>
 #include <linux/if_addr.h>
+
 /* glibc versions prior to 2.24 do not define SOL_NETLINK */
 #ifndef SOL_NETLINK
 #define SOL_NETLINK 270
 /* glibc versions prior to 2.24 do not define SOL_NETLINK */
 #ifndef SOL_NETLINK
 #define SOL_NETLINK 270
 #ifndef NETLINK_CAP_ACK
 #define NETLINK_CAP_ACK 10
 #endif
 #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
 
 #endif
 
-#include "pppd.h"
+#include "pppd-private.h"
+#include "options.h"
 #include "fsm.h"
 #include "ipcp.h"
 
 #include "fsm.h"
 #include "ipcp.h"
 
 #include "eui64.h"
 #endif /* 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>
 #ifdef PPP_WITH_FILTER
 #include <pcap-bpf.h>
 #include <linux/filter.h>
@@ -290,6 +291,147 @@ extern int dfl_route_metric;
     memset ((char *) &(addr), '\0', sizeof(addr));     \
     addr.sa_family = (family);
 
     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.
  */
 /*
  * Determine if the PPP connection should still be present.
  */
@@ -398,10 +540,10 @@ 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
  */
 void
-sys_close(void)
+ppp_sys_close(void)
 {
     if (new_style_driver && ppp_dev_fd >= 0)
        close(ppp_dev_fd);
 {
     if (new_style_driver && ppp_dev_fd >= 0)
        close(ppp_dev_fd);
@@ -465,7 +607,7 @@ int tty_establish_ppp (int tty_fd)
 #ifndef N_SYNC_PPP
 #define N_SYNC_PPP 14
 #endif
 #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");
     if (ioctl(tty_fd, TIOCSETD, &ppp_disc) < 0) {
        if ( ! ok_error (errno) ) {
            error("Couldn't set tty to PPP discipline: %m");
@@ -473,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 \
 
 #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 \
@@ -494,7 +636,7 @@ int tty_establish_ppp (int tty_fd)
  *
  * generic_establish_ppp - Turn the fd into a ppp interface.
  */
  *
  * generic_establish_ppp - Turn the fd into a ppp interface.
  */
-int generic_establish_ppp (int fd)
+int ppp_generic_establish (int fd)
 {
     int x;
 
 {
     int x;
 
@@ -631,16 +773,16 @@ void tty_disestablish_ppp(int tty_fd)
 flushfailed:
     initfdflags = -1;
 
 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().
  */
  * 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);
 {
     if (new_style_driver) {
        close(ppp_fd);
@@ -648,7 +790,7 @@ void generic_disestablish_ppp(int dev_fd)
        if (demand) {
            modify_flags(ppp_dev_fd, 0, SC_LOOP_TRAFFIC);
            looped = 1;
        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;
            close(ppp_dev_fd);
            remove_fd(ppp_dev_fd);
            ppp_dev_fd = -1;
@@ -662,6 +804,84 @@ 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;
+    int resp;
+
+    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;
+
+    /*
+     * 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);
+
+    if (resp) {
+        /*
+         * 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 = (resp < 0) ? -resp : 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.
 /*
  * make_ppp_unit - make a new ppp unit for ppp_dev_fd.
  * Assumes new_style_driver.
@@ -682,6 +902,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");
 
            || 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 = req_unit;
        x = ioctl(ppp_dev_fd, PPPIOCNEWUNIT, &ifunit);
        if (x < 0 && req_unit >= 0 && errno == EEXIST) {
@@ -689,6 +936,11 @@ static int make_ppp_unit(void)
                ifunit = -1;
                x = ioctl(ppp_dev_fd, PPPIOCNEWUNIT, &ifunit);
        }
                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");
 
        if (x < 0)
                error("Couldn't create new ppp unit: %m");
 
@@ -1244,7 +1496,7 @@ int read_packet (unsigned char *buf)
            error("read /dev/ppp: %m");
        if (nr < 0 && errno == ENXIO)
            nr = 0;
            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;
        }
            remove_fd(ppp_dev_fd);
            bundle_eof = 1;
        }
@@ -1290,7 +1542,7 @@ get_loop_output(void)
  * netif_set_mtu - set the MTU on the PPP network interface.
  */
 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;
 
 {
     struct ifreq ifr;
 
@@ -1306,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 - get the MTU on the PPP network interface.
  */
 int
-netif_get_mtu(int unit)
+ppp_get_mtu(int unit)
 {
     struct ifreq ifr;
 
 {
     struct ifreq ifr;
 
@@ -1341,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)
        }
 
        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);
 }
 
        modify_flags(ppp_fd, SC_COMP_PROT|SC_COMP_AC|SC_SYNC, x);
 }
 
@@ -1514,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
 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 {
         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;
 
     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;
 
     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;
     }
 
        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:
 
     return 1;
 err:
-    close(rtnl_fd);
-    rtnl_fd = -1;
+    close(fd);
+    fd = -1;
+#endif
     return 0;
 }
 
     return 0;
 }
 
@@ -2007,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;
         * - 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) {
        /*
     } 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);
 
         */
        u_int32_t old_gateway = SIN_ADDR(old_def_rt.rt_gateway);
 
@@ -2666,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).
  */
 
  * (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;
 {
     int s, ok, fd;
     struct ifreq ifr;
@@ -3167,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 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);
 
     memset(&nlreq, 0, sizeof(nlreq));
     nlreq.nlh.nlmsg_len = sizeof(nlreq);
@@ -3233,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);
 
     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.
          */
         /*
          * 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))
         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;
     }
 
         return 0;
     }
 
@@ -3573,7 +3685,7 @@ int
 get_host_seed(void)
 {
     int h;
 get_host_seed(void)
 {
     int h;
-    char *p = hostname;
+    const char *p;
 
     h = 407;
     for (p = hostname; *p != 0; ++p)
 
     h = 407;
     for (p = hostname; *p != 0; ++p)
@@ -3590,7 +3702,7 @@ int
 sys_check_options(void)
 {
     if (demand && driver_is_old) {
 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;
                     "version %d.%d.%d", driver_version, driver_modification,
                     driver_patch);
        return 0;
@@ -3607,7 +3719,7 @@ sys_check_options(void)
  * get_time - Get current time, monotonic if possible.
  */
 int
  * 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. */
 {
 /* Old glibc (< 2.3.4) does define CLOCK_MONOTONIC, but kernel may have it.
  * Runtime checking makes it safe. */