Merge pull request #366 from pali/rtnetlink-register
[ppp.git] / pppd / sys-linux.c
index 6eed86f3f2a2b0bd6f1754375d1c6734380ce6a2..7beb977484f5ddca6859e10f79dcab039e26ea9c 100644 (file)
@@ -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.
  */
 #define IFLA_STATS_LINK_64 1
 #endif
 
+#include <linux/if_addr.h>
+
 /* glibc versions prior to 2.24 do not define SOL_NETLINK */
 #ifndef SOL_NETLINK
 #define SOL_NETLINK 270
 #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.
@@ -200,7 +202,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 +214,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 +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.
  */
@@ -357,7 +500,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 +526,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
@@ -411,7 +556,7 @@ sys_close(void)
        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
@@ -697,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);
@@ -747,63 +864,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 +880,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 +943,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");
 
@@ -1612,7 +1686,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 +1711,7 @@ int set_filters(struct bpf_program *pass, struct bpf_program *active)
        }
        return 1;
 }
-#endif /* PPP_FILTER */
+#endif /* PPP_WITH_FILTER */
 
 /********************************************************************
  *
@@ -1662,20 +1736,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 +1773,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)) {
-           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;
     return 0;
 }
 
@@ -2293,7 +2300,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 +2490,7 @@ int cif6defaultroute (int unit, eui64_t ouraddr, eui64_t gateway)
 
     return 1;
 }
-#endif /* INET6 */
+#endif /* PPP_WITH_IPV6CP */
 
 /********************************************************************
  *
@@ -3109,15 +3116,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 +3155,7 @@ int sif6down (int u)
 
     return setifstate(u, 0);
 }
-#endif /* INET6 */
+#endif /* PPP_WITH_IPV6CP */
 
 /********************************************************************
  *
@@ -3336,7 +3343,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 +3358,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 +3388,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 +3514,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